This commit is contained in:
Artur Weigandt 2025-05-25 18:26:46 +02:00 committed by GitHub
commit 71b6334828
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 663 additions and 45 deletions

View file

@ -11,6 +11,8 @@ use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Cache\Enum; use Friendica\Core\Cache\Enum;
use Friendica\Core\Cache\Exception\CachePersistenceException; use Friendica\Core\Cache\Exception\CachePersistenceException;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Repository\CacheRepository;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
/** /**
@ -25,11 +27,16 @@ class DatabaseCache extends AbstractCache implements ICanCache
*/ */
private $dba; private $dba;
private CacheRepository $cacheRepo;
public function __construct(string $hostname, Database $dba) public function __construct(string $hostname, Database $dba)
{ {
parent::__construct($hostname); parent::__construct($hostname);
$this->dba = $dba; $this->dba = $dba;
// #TODO: Replace this with constuctor injection
$this->cacheRepo = DI::databaseService()->getCacheRepository();
} }
/** /**
@ -41,27 +48,14 @@ class DatabaseCache extends AbstractCache implements ICanCache
{ {
try { try {
if (empty($prefix)) { if (empty($prefix)) {
$where = ['`expires` >= ?', DateTimeFormat::utcNow()]; $keys = $this->cacheRepo->getAllKeysValidUntil(DateTimeFormat::utcNow());
} else { } else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', DateTimeFormat::utcNow(), $prefix]; $keys = $this->cacheRepo->getAllKeysValidUntilWithPrefix(DateTimeFormat::utcNow(), $prefix);
} }
$stmt = $this->dba->select('cache', ['k'], $where);
} catch (\Exception $exception) { } catch (\Exception $exception) {
throw new CachePersistenceException(sprintf('Cannot fetch all keys with prefix %s', $prefix), $exception); throw new CachePersistenceException(sprintf('Cannot fetch all keys with prefix %s', $prefix), $exception);
} }
try {
$keys = [];
while ($key = $this->dba->fetch($stmt)) {
array_push($keys, $key['k']);
}
} catch (\Exception $exception) {
$this->dba->close($stmt);
throw new CachePersistenceException(sprintf('Cannot fetch all keys with prefix %s', $prefix), $exception);
}
$this->dba->close($stmt);
return $keys; return $keys;
} }

View file

@ -80,6 +80,11 @@ abstract class DI
return self::$dice->create(AppHelper::class); return self::$dice->create(AppHelper::class);
} }
public static function databaseService(): Database\DatabaseService
{
return self::$dice->create(Database\DatabaseService::class);
}
/** /**
* @return Database\Database * @return Database\Database
*/ */

View file

@ -62,15 +62,15 @@ class Database
protected $server_info = ''; protected $server_info = '';
/** @var PDO|mysqli|null */ /** @var PDO|mysqli|null */
protected $connection; protected $connection;
protected $driver = ''; protected $driver = '';
protected $pdo_emulate_prepares = false; protected $pdo_emulate_prepares = false;
private $error = ''; private $error = '';
private $errorno = 0; private $errorno = 0;
private $affected_rows = 0; private $affected_rows = 0;
protected $in_transaction = false; protected $in_transaction = false;
protected $in_retrial = false; protected $in_retrial = false;
protected $testmode = false; private bool $throwExceptionsOnErrors = false;
private $relation = []; private $relation = [];
/** @var DbaDefinition */ /** @var DbaDefinition */
protected $dbaDefinition; protected $dbaDefinition;
/** @var ViewDefinition */ /** @var ViewDefinition */
@ -205,9 +205,18 @@ class Database
return $this->connected; return $this->connected;
} }
public function setTestmode(bool $test) /**
* Should errors throwns as exceptions?
*
* @return bool returns the previous value
*/
public function throwExceptionsOnErrors(bool $throwExceptions): bool
{ {
$this->testmode = $test; $prev = $this->throwExceptionsOnErrors;
$this->throwExceptionsOnErrors = $throwExceptions;
return $prev;
} }
/** /**
@ -672,7 +681,7 @@ class Database
$error = $this->error; $error = $this->error;
$errorno = $this->errorno; $errorno = $this->errorno;
if ($this->testmode) { if ($this->throwExceptionsOnErrors) {
throw new DatabaseException($error, $errorno, $this->replaceParameters($sql, $args)); throw new DatabaseException($error, $errorno, $this->replaceParameters($sql, $args));
} }
@ -779,7 +788,7 @@ class Database
$error = $this->error; $error = $this->error;
$errorno = $this->errorno; $errorno = $this->errorno;
if ($this->testmode) { if ($this->throwExceptionsOnErrors) {
throw new DatabaseException($error, $errorno, $this->replaceParameters($sql, $params)); throw new DatabaseException($error, $errorno, $this->replaceParameters($sql, $params));
} }

View file

@ -0,0 +1,36 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Database;
use Friendica\Database\Database;
use Friendica\Database\Repository\CacheTableRepository;
use Friendica\Database\Repository\UserdTableRepository;
use Friendica\Repository\CacheRepository;
use Friendica\Repository\DeletedUserRepository;
final class DatabaseService
{
private Database $database;
public function __construct(Database $database)
{
$this->database = $database;
}
public function getDeletedUserRepository(): DeletedUserRepository
{
return new UserdTableRepository($this->database);
}
public function getCacheRepository(): CacheRepository
{
return new CacheTableRepository($this->database);
}
}

View file

@ -0,0 +1,83 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Database\Model;
use Exception;
use Friendica\Database\DBA;
use Friendica\Entity\CacheEntity;
/**
* Model for a row in the cache table
*/
final class CacheModel implements CacheEntity
{
/**
* Validates the row array and creates the entity
*
* @throws Exception If $data['v'] contains invalid data that could not unserialize.
*/
public static function createFromArray(array $data): self
{
$rawValue = array_key_exists('v', $data) ? (string) $data['v'] : '';
$value = @unserialize($rawValue);
// Only return a value if the serialized value is valid.
// We also check if the db entry is a serialized
// boolean 'false' value (which we want to return).
if ($value === false && $rawValue !== 'b:0;' ) {
throw new Exception(sprintf('Invalid value data for cache object.'));
}
$entity = new self();
array_key_exists('k', $data) ?? $entity->k = (string) $data['k'];
$entity->v = $rawValue;
$entity->value = $value;
array_key_exists('expired', $data) ?? $entity->expired = (string) $data['expired'];
array_key_exists('updated', $data) ?? $entity->updated = (string) $data['updated'];
return $entity;
}
/**
* cache key
*/
private string $k = '';
/**
* cached serialized value
*/
private string $v = '';
/**
*
* @var mixed $value cached unserialized value
*/
private $value;
/**
* datetime of cache expiration
*/
private string $expired = DBA::NULL_DATETIME;
/**
* datetime of cache insertion
*/
private string $updated = DBA::NULL_DATETIME;
private function __construct() {}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
}

View file

@ -0,0 +1,135 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Database\Repository;
use Friendica\Database\Database;
use Friendica\Database\DatabaseException;
use Friendica\Database\Model\CacheModel;
use Friendica\Entity\CacheEntity;
use Friendica\Repository\CacheRepository;
use Throwable;
/**
* Repository for cache table
*/
final class CacheTableRepository implements CacheRepository
{
private Database $database;
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntil(string $expires): array
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
return $this->getAllKeys($expires, null);
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException('Cannot fetch all keys without prefix', 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntilWithPrefix(string $expires, string $prefix): array
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
return $this->getAllKeys($expires, $prefix);
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException(sprintf('Cannot fetch all keys with prefix `%s`', $prefix), 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/**
* @throws DatabaseException
*
* @return CacheEntity|null
*/
public function findOneByKeyValidUntil(string $key, string $expires)
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
$cacheArray = $this->database->selectFirst(
'cache',
['v'],
['`k` = ? AND (`expires` >= ? OR `expires` = -1)', $key, $expires]
);
if (!$this->database->isResult($cacheArray)) {
return null;
}
} catch (Throwable $th) {
if (! $th instanceof DatabaseException) {
$th = new DatabaseException(sprintf('Cannot get cache entry with key `%s`', $key), 0, '', $th);
}
throw $th;
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
try {
$entity = CacheModel::createFromArray($cacheArray);
} catch (Throwable $th) {
return null;
}
return $entity;
}
private function getAllKeys(string $expires, ?string $prefix = null): array
{
if ($prefix === null) {
$where = ['`expires` >= ?', $expires];
} else {
$where = ['`expires` >= ? AND `k` LIKE CONCAT(?, \'%\')', $expires, $prefix];
}
$stmt = $this->database->select('cache', ['k'], $where);
$keys = [];
try {
while ($key = $this->database->fetch($stmt)) {
array_push($keys, $key['k']);
}
} finally {
$this->database->close($stmt);
}
return $keys;
}
}

View file

@ -0,0 +1,54 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Database\Repository;
use Exception;
use Friendica\Database\Database;
use Friendica\Database\DatabaseException;
use Friendica\Repository\DeletedUserRepository;
/**
* Repository for userd table
*/
final class UserdTableRepository implements DeletedUserRepository
{
private Database $database;
public function __construct(Database $database)
{
$this->database = $database;
}
/**
* Insert a deleted user by username.
*
* @throws DatabaseException If the username could not be inserted
*/
public function insertByUsername(string $username): void
{
$throw = $this->database->throwExceptionsOnErrors(true);
try {
$this->database->insert('userd', ['username' => $username]);
} finally {
$this->database->throwExceptionsOnErrors($throw);
}
}
/**
* Check if a deleted username exists.
*
* @throws \Exception
*/
public function existsByUsername(string $username): bool
{
return $this->database->exists('userd', ['username' => $username]);
}
}

View file

@ -0,0 +1,21 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Entity;
/**
* Entity for a cache entry
*/
interface CacheEntity
{
/**
* @return mixed
*/
public function getValue();
}

View file

@ -266,10 +266,12 @@ class User
return $system_actor_name; return $system_actor_name;
} }
$userDeletedRepository = DI::databaseService()->getDeletedUserRepository();
// List of possible actor names // List of possible actor names
$possible_accounts = ['friendica', 'actor', 'system', 'internal']; $possible_accounts = ['friendica', 'actor', 'system', 'internal'];
foreach ($possible_accounts as $name) { foreach ($possible_accounts as $name) {
if (!DBA::exists('user', ['nickname' => $name]) && !DBA::exists('userd', ['username' => $name])) { if (!DBA::exists('user', ['nickname' => $name]) && !$userDeletedRepository->existsByUsername($name)) {
DI::config()->set('system', 'actor_name', $name); DI::config()->set('system', 'actor_name', $name);
return $name; return $name;
} }
@ -1299,10 +1301,12 @@ class User
throw new Exception(DI::l10n()->t('Your nickname can only contain a-z, 0-9 and _.')); throw new Exception(DI::l10n()->t('Your nickname can only contain a-z, 0-9 and _.'));
} }
$userDeletedRepository = DI::databaseService()->getDeletedUserRepository();
// Check existing and deleted accounts for this nickname. // Check existing and deleted accounts for this nickname.
if ( if (
DBA::exists('user', ['nickname' => $nickname]) DBA::exists('user', ['nickname' => $nickname])
|| DBA::exists('userd', ['username' => $nickname]) || $userDeletedRepository->existsByUsername($nickname)
) { ) {
throw new Exception(DI::l10n()->t('Nickname is already registered. Please choose another.')); throw new Exception(DI::l10n()->t('Nickname is already registered. Please choose another.'));
} }
@ -1812,9 +1816,15 @@ class User
$user = $hook_data['user'] ?? $user; $user = $hook_data['user'] ?? $user;
$userDeletedRepository = DI::databaseService()->getDeletedUserRepository();
// save username (actually the nickname as it is guaranteed // save username (actually the nickname as it is guaranteed
// unique), so it cannot be re-registered in the future. // unique), so it cannot be re-registered in the future.
DBA::insert('userd', ['username' => $user['nickname']]); try {
$userDeletedRepository->insertByUsername($user['nickname']);
} catch (\Throwable $th) {
DI::logger()->error('Error while inserting username of deleted user.', ['username' => $user['nickname'], 'exception' => $th]);
}
// Remove all personal settings, especially connector settings // Remove all personal settings, especially connector settings
DBA::delete('pconfig', ['uid' => $uid]); DBA::delete('pconfig', ['uid' => $uid]);

View file

@ -35,6 +35,7 @@ use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Profile\ProfileField\Repository\ProfileField; use Friendica\Profile\ProfileField\Repository\ProfileField;
use Friendica\Protocol\ActivityPub; use Friendica\Protocol\ActivityPub;
use Friendica\Repository\DeletedUserRepository;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network; use Friendica\Util\Network;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
@ -47,6 +48,7 @@ class Profile extends BaseProfile
{ {
/** @var Database */ /** @var Database */
private $database; private $database;
private DeletedUserRepository $deletedUserRepository;
/** @var AppHelper */ /** @var AppHelper */
private $appHelper; private $appHelper;
/** @var IHandleUserSessions */ /** @var IHandleUserSessions */
@ -66,6 +68,7 @@ class Profile extends BaseProfile
IHandleUserSessions $session, IHandleUserSessions $session,
AppHelper $appHelper, AppHelper $appHelper,
Database $database, Database $database,
DeletedUserRepository $deletedUserRepository,
EventDispatcherInterface $eventDispatcher, EventDispatcherInterface $eventDispatcher,
L10n $l10n, L10n $l10n,
BaseURL $baseUrl, BaseURL $baseUrl,
@ -78,13 +81,14 @@ class Profile extends BaseProfile
) { ) {
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->database = $database; $this->database = $database;
$this->appHelper = $appHelper; $this->deletedUserRepository = $deletedUserRepository;
$this->session = $session; $this->appHelper = $appHelper;
$this->config = $config; $this->session = $session;
$this->page = $page; $this->config = $config;
$this->profileField = $profileField; $this->page = $page;
$this->eventDispatcher = $eventDispatcher; $this->profileField = $profileField;
$this->eventDispatcher = $eventDispatcher;
} }
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
@ -102,7 +106,7 @@ class Profile extends BaseProfile
} }
} }
if ($this->database->exists('userd', ['username' => $this->parameters['nickname']])) { if ($this->deletedUserRepository->existsByUsername($this->parameters['nickname'])) {
// Known deleted user // Known deleted user
$data = ActivityPub\Transmitter::getDeletedUser($this->parameters['nickname']); $data = ActivityPub\Transmitter::getDeletedUser($this->parameters['nickname']);

View file

@ -7,7 +7,8 @@
namespace Friendica\Module\User; namespace Friendica\Module\User;
use Friendica\App; use Friendica\App\Arguments;
use Friendica\App\BaseURL;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
@ -26,6 +27,7 @@ use Friendica\Navigation\SystemMessages;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Object\Image; use Friendica\Object\Image;
use Friendica\Protocol\Delivery; use Friendica\Protocol\Delivery;
use Friendica\Repository\DeletedUserRepository;
use Friendica\Security\PermissionSet\Repository\PermissionSet; use Friendica\Security\PermissionSet\Repository\PermissionSet;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Friendica\Util\Strings; use Friendica\Util\Strings;
@ -47,20 +49,38 @@ class Import extends \Friendica\BaseModule
/** @var Database */ /** @var Database */
private $database; private $database;
private DeletedUserRepository $deletedUserRepository;
/** @var PermissionSet */ /** @var PermissionSet */
private $permissionSet; private $permissionSet;
/** @var UserSession */ /** @var UserSession */
private $session; private $session;
public function __construct(UserSession $session, PermissionSet $permissionSet, IManagePersonalConfigValues $pconfig, Database $database, SystemMessages $systemMessages, IManageConfigValues $config, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) public function __construct(
{ UserSession $session,
PermissionSet $permissionSet,
IManagePersonalConfigValues $pconfig,
Database $database,
DeletedUserRepository $deletedUserRepository,
SystemMessages $systemMessages,
IManageConfigValues $config,
L10n $l10n,
BaseURL $baseUrl,
Arguments $args,
LoggerInterface $logger,
Profiler $profiler,
Response $response,
array $server,
array $parameters = []
) {
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config; $this->config = $config;
$this->pconfig = $pconfig; $this->pconfig = $pconfig;
$this->systemMessages = $systemMessages; $this->systemMessages = $systemMessages;
$this->database = $database; $this->database = $database;
$this->deletedUserRepository = $deletedUserRepository;
$this->permissionSet = $permissionSet; $this->permissionSet = $permissionSet;
$this->session = $session; $this->session = $session;
} }
@ -213,7 +233,7 @@ class Import extends \Friendica\BaseModule
// check for username // check for username
// check if username matches deleted account // check if username matches deleted account
if ($this->database->exists('user', ['nickname' => $account['user']['nickname']]) if ($this->database->exists('user', ['nickname' => $account['user']['nickname']])
|| $this->database->exists('userd', ['username' => $account['user']['nickname']])) { || $this->deletedUserRepository->existsByUsername($account['user']['nickname'])) {
$this->systemMessages->addNotice($this->t("User '%s' already exists on this server!", $account['user']['nickname'])); $this->systemMessages->addNotice($this->t("User '%s' already exists on this server!", $account['user']['nickname']));
return; return;
} }

View file

@ -0,0 +1,40 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Repository;
use Friendica\Database\DatabaseException;
use Friendica\Entity\CacheEntity;
/**
* Interface for a cache repository
*/
interface CacheRepository
{
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntil(string $expires): array;
/**
* @throws DatabaseException
*
* @return array<string>
*/
public function getAllKeysValidUntilWithPrefix(string $expires, string $prefix): array;
/**
* @throws DatabaseException
*
* @return CacheEntity|null
*/
public function findOneByKeyValidUntil(string $key, string $expires);
}

View file

@ -0,0 +1,33 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Repository;
use Exception;
use Friendica\Database\DatabaseException;
/**
* Interface for a repository for deleted users
*/
interface DeletedUserRepository
{
/**
* Insert a deleted user by username.
*
* @throws DatabaseException If the username could not be inserted
*/
public function insertByUsername(string $username): void;
/**
* Check if a deleted username exists.
*
* @throws \Exception
*/
public function existsByUsername(string $username): bool;
}

View file

@ -60,7 +60,7 @@ trait FixtureTestTrait
/** @var Database $dba */ /** @var Database $dba */
$dba = $this->dice->create(Database::class); $dba = $this->dice->create(Database::class);
$dba->setTestmode(true); $dba->throwExceptionsOnErrors(true);
DBStructure::checkInitialValues(); DBStructure::checkInitialValues();

View file

@ -0,0 +1,97 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Database\Repository;
use Friendica\Database\Database;
use Friendica\Database\DatabaseException;
use Friendica\Database\Repository\CacheTableRepository;
use PHPUnit\Framework\TestCase;
use Throwable;
class CacheTableRepositoryTest extends TestCase
{
public function testGetAllKeysValidUntilReturnsArray(): void
{
$stmt = new \stdClass;
$database = $this->createStub(Database::class);
$database->method('select')->willReturnMap([
['cache', ['k'], ['`expires` >= ?', '2025-04-16 10:12:01'], [], $stmt],
]);
$database->method('fetch')->willReturnOnConsecutiveCalls(
['k' => 'value1'],
['k' => 'value2'],
['k' => 'value3'],
false
);
$repo = new CacheTableRepository($database);
$this->assertSame(
[
'value1',
'value2',
'value3',
],
$repo->getAllKeysValidUntil('2025-04-16 10:12:01')
);
}
public function testGetAllKeysValidUntilThrowsException(): void
{
$database = $this->createStub(Database::class);
$database->method('select')->willThrowException($this->createStub(Throwable::class));
$repo = new CacheTableRepository($database);
$this->expectException(DatabaseException::class);
$repo->getAllKeysValidUntil('2025-04-16 10:12:01');
}
public function testGetAllKeysValidUntilWithPrefixReturnsArray(): void
{
$stmt = new \stdClass;
$database = $this->createStub(Database::class);
$database->method('select')->willReturnMap([
['cache', ['k'], ['`expires` >= ?', '2025-04-16 10:12:01'], [], $stmt],
]);
$database->method('fetch')->willReturnOnConsecutiveCalls(
['k' => 'value1'],
['k' => 'value2'],
['k' => 'value3'],
false
);
$repo = new CacheTableRepository($database);
$this->assertSame(
[
'value1',
'value2',
'value3',
],
$repo->getAllKeysValidUntilWithPrefix('2025-04-16 10:12:01', 'prefix')
);
}
public function testGetAllKeysValidUntilWithPrefixThrowsException(): void
{
$database = $this->createStub(Database::class);
$database->method('select')->willThrowException($this->createStub(Throwable::class));
$repo = new CacheTableRepository($database);
$this->expectException(DatabaseException::class);
$repo->getAllKeysValidUntilWithPrefix('2025-04-16 10:12:01', 'prefix');
}
}

View file

@ -0,0 +1,77 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Database\Repository;
use Friendica\Database\Database;
use Friendica\Database\DatabaseException;
use Friendica\Database\Repository\UserdTableRepository;
use Friendica\Repository\DeletedUserRepository;
use PHPUnit\Framework\TestCase;
class UserdTableRepositoryTest extends TestCase
{
public function testImplementationOfInterfaces(): void
{
$repo = new UserdTableRepository($this->createMock(Database::class));
$this->assertInstanceOf(DeletedUserRepository::class, $repo);
}
public function testInsertByUsernameCallsDatabase(): void
{
$database = $this->createMock(Database::class);
$database->expects($this->once())->method('insert')->willReturnMap([
['userd', ['username' => 'test'], 0, true],
]);
$repo = new UserdTableRepository($database);
$repo->insertByUsername('test');
}
public function testInsertByUsernameThrowsException(): void
{
$database = $this->createMock(Database::class);
$database->expects($this->exactly(2))->method('throwExceptionsOnErrors');
$database->expects($this->once())->method('insert')->willThrowException(
new DatabaseException('An error occured.', 0, 'SQL query')
);
$repo = new UserdTableRepository($database);
$this->expectException(DatabaseException::class);
$repo->insertByUsername('test');
}
public function testExistsByUsernameReturnsTrue(): void
{
$database = $this->createStub(Database::class);
$database->method('exists')->willReturnMap([
['userd', ['username' => 'test'], true],
]);
$repo = new UserdTableRepository($database);
$this->assertTrue($repo->existsByUsername('test'));
}
public function testExistsByUsernameReturnsFalse(): void
{
$database = $this->createStub(Database::class);
$database->method('exists')->willReturnMap([
['userd', ['username' => 'test'], false],
]);
$repo = new UserdTableRepository($database);
$this->assertFalse($repo->existsByUsername('test'));
}
}

View file

@ -43,7 +43,7 @@ trait CreateDatabaseTrait
])); ]));
$database = new StaticDatabase($config, (new DbaDefinition($this->root->url()))->load(), (new ViewDefinition($this->root->url()))->load()); $database = new StaticDatabase($config, (new DbaDefinition($this->root->url()))->load(), (new ViewDefinition($this->root->url()))->load());
$database->setTestmode(true); $database->throwExceptionsOnErrors(true);
return $database; return $database;
} }