v1.691: testy jednostkowe Domain\, infrastruktura PHPUnit, paczka aktualizacji

- Dodano testy: SettingsRepositoryTest, LanguagesRepositoryTest, UserRepositoryTest
- Infrastruktura: phpunit.xml, composer.json (phpunit/phpunit ^10), tests/bootstrap.php
- Bootstrap stuby: \Shared\Cache\CacheHandler (in-memory), \S
- Zaktualizowano docs/TESTING.md dla cmsPRO
- Paczka: updates/1.60/ver_1.691.zip + manifest

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 23:50:33 +01:00
parent 9ee4116f50
commit 8e6b29976c
11 changed files with 641 additions and 91 deletions

View File

@@ -0,0 +1,123 @@
<?php
namespace Tests\Unit\Domain\Languages;
use Domain\Languages\LanguagesRepository;
use PHPUnit\Framework\TestCase;
class LanguagesRepositoryTest extends TestCase
{
private function mockDb(): object
{
return $this->createMock(\medoo::class);
}
protected function setUp(): void
{
\Shared\Cache\CacheHandler::reset();
}
// --- languagesList ---
public function testLanguagesListReturnsArray(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn([['id' => 'pl', 'name' => 'Polski']]);
$repo = new LanguagesRepository($db);
$this->assertSame([['id' => 'pl', 'name' => 'Polski']], $repo->languagesList());
}
public function testLanguagesListReturnsEmptyWhenNull(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn(null);
$repo = new LanguagesRepository($db);
$this->assertSame([], $repo->languagesList());
}
// --- languageDetails ---
public function testLanguageDetailsReturnsRowWhenFound(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(['id' => 'pl', 'name' => 'Polski']);
$repo = new LanguagesRepository($db);
$this->assertSame('pl', $repo->languageDetails('pl')['id']);
}
public function testLanguageDetailsReturnsNullWhenNotFound(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(null);
$repo = new LanguagesRepository($db);
$this->assertNull($repo->languageDetails('xx'));
}
// --- activeLanguages ---
public function testActiveLanguagesQueriesDbAndCaches(): void
{
$expected = [['id' => 'pl', 'name' => 'Polski', 'domain' => null]];
$db = $this->mockDb();
$db->expects($this->once())->method('select')->willReturn($expected);
$repo = new LanguagesRepository($db);
$this->assertSame($expected, $repo->activeLanguages());
// Drugi odczyt — z cache (mock select nie zostanie wywołany drugi raz)
$this->assertSame($expected, $repo->activeLanguages());
}
public function testActiveLanguagesReturnsEmptyWhenNull(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn(null);
$repo = new LanguagesRepository($db);
$this->assertSame([], $repo->activeLanguages());
}
// --- maxOrder ---
public function testMaxOrderReturnsInteger(): void
{
$db = $this->mockDb();
$db->method('max')->willReturn('5');
$repo = new LanguagesRepository($db);
$this->assertSame(5, $repo->maxOrder());
}
// --- translationDelete ---
public function testTranslationDeleteReturnsTrueOnSuccess(): void
{
$db = $this->mockDb();
$db->method('delete')->willReturn(1);
$repo = new LanguagesRepository($db);
$this->assertTrue($repo->translationDelete(1));
}
public function testTranslationDeleteReturnsFalseOnFailure(): void
{
$db = $this->mockDb();
$db->method('delete')->willReturn(0);
$repo = new LanguagesRepository($db);
$this->assertFalse($repo->translationDelete(1));
}
// --- translationDetails ---
public function testTranslationDetailsReturnsRowOrNull(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(['id' => 1, 'text' => 'hello']);
$repo = new LanguagesRepository($db);
$this->assertSame(['id' => 1, 'text' => 'hello'], $repo->translationDetails(1));
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Tests\Unit\Domain\Settings;
use Domain\Settings\SettingsRepository;
use PHPUnit\Framework\TestCase;
class SettingsRepositoryTest extends TestCase
{
private function mockDb(): object
{
return $this->createMock(\medoo::class);
}
protected function setUp(): void
{
\Shared\Cache\CacheHandler::reset();
}
// --- allSettings ---
public function testAllSettingsReturnsMappedArray(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn([
['param' => 'site_name', 'value' => 'Test CMS'],
['param' => 'email', 'value' => 'admin@test.pl'],
]);
$repo = new SettingsRepository($db);
$result = $repo->allSettings();
$this->assertSame('Test CMS', $result['site_name']);
$this->assertSame('admin@test.pl', $result['email']);
}
public function testAllSettingsReturnsEmptyArrayWhenDbReturnsNull(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn(null);
$repo = new SettingsRepository($db);
$this->assertSame([], $repo->allSettings());
}
public function testAllSettingsUsesCache(): void
{
$db = $this->mockDb();
$db->expects($this->never())->method('select');
\CacheHandlerStub::store('settings_details', ['cached' => '1']);
$repo = new SettingsRepository($db);
$result = $repo->allSettings();
$this->assertSame('1', $result['cached']);
}
// --- update ---
public function testUpdateCallsDbUpdateWhenParamExists(): void
{
$db = $this->mockDb();
$db->method('count')->willReturn(1);
$db->expects($this->once())->method('update')->willReturn(true);
$db->expects($this->never())->method('insert');
$repo = new SettingsRepository($db);
$this->assertTrue($repo->update('site_name', 'Nowa Nazwa'));
}
public function testUpdateCallsDbInsertWhenParamMissing(): void
{
$db = $this->mockDb();
$db->method('count')->willReturn(0);
$db->expects($this->once())->method('insert')->willReturn(true);
$db->expects($this->never())->method('update');
$repo = new SettingsRepository($db);
$repo->update('new_param', 'value');
}
// --- visitCounter ---
public function testVisitCounterReturnsValue(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn('1234');
$repo = new SettingsRepository($db);
$this->assertSame('1234', $repo->visitCounter());
}
public function testVisitCounterReturnsNullWhenEmpty(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(null);
$repo = new SettingsRepository($db);
$this->assertNull($repo->visitCounter());
}
}

View File

@@ -0,0 +1,252 @@
<?php
namespace Tests\Unit\Domain\User;
use Domain\User\UserRepository;
use PHPUnit\Framework\TestCase;
class UserRepositoryTest extends TestCase
{
private function mockDb(): object
{
return $this->createMock(\medoo::class);
}
protected function setUp(): void
{
\Shared\Cache\CacheHandler::reset();
}
// --- find ---
public function testFindReturnsUserArray(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(['id' => 1, 'login' => 'admin']);
$repo = new UserRepository($db);
$this->assertSame('admin', $repo->find(1)['login']);
}
public function testFindReturnsNullWhenNotFound(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(null);
$repo = new UserRepository($db);
$this->assertNull($repo->find(99));
}
// --- findByLogin ---
public function testFindByLoginReturnsUser(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(['id' => 1, 'login' => 'admin']);
$repo = new UserRepository($db);
$this->assertNotNull($repo->findByLogin('admin'));
}
// --- all ---
public function testAllReturnsArray(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn([['id' => 1], ['id' => 2]]);
$repo = new UserRepository($db);
$this->assertCount(2, $repo->all());
}
public function testAllReturnsEmptyArrayWhenNull(): void
{
$db = $this->mockDb();
$db->method('select')->willReturn(null);
$repo = new UserRepository($db);
$this->assertSame([], $repo->all());
}
// --- hasPrivilege ---
public function testHasPrivilegeReturnsTrueForAdminUser(): void
{
$db = $this->mockDb();
$repo = new UserRepository($db);
// userId === 1 zawsze ma uprawnienia, bez zapytania do DB
$db->expects($this->never())->method('count');
$this->assertTrue($repo->hasPrivilege('articles', 1));
}
public function testHasPrivilegeReturnsTrueWhenPrivilegeExists(): void
{
$db = $this->mockDb();
$db->method('count')->willReturn(1);
$repo = new UserRepository($db);
$this->assertTrue($repo->hasPrivilege('articles', 2));
}
public function testHasPrivilegeReturnsFalseWhenPrivilegeMissing(): void
{
$db = $this->mockDb();
$db->method('count')->willReturn(0);
$repo = new UserRepository($db);
$this->assertFalse($repo->hasPrivilege('articles', 2));
}
// --- logon ---
public function testLogonReturnsZeroWhenUserNotFound(): void
{
$db = $this->mockDb();
// Pierwsze get() (sprawdź czy login istnieje) → null
$db->method('get')->willReturn(null);
$repo = new UserRepository($db);
$this->assertSame(0, $repo->logon('unknown', 'pass'));
}
public function testLogonReturnsMinusOneWhenAccountBlocked(): void
{
$db = $this->mockDb();
// Pierwsze get() → użytkownik istnieje, drugie → konto zablokowane (null)
$db->method('get')->willReturnOnConsecutiveCalls(
['id' => 2, 'login' => 'user'],
null
);
$repo = new UserRepository($db);
$this->assertSame(-1, $repo->logon('user', 'pass'));
}
public function testLogonReturnsOneOnSuccess(): void
{
$db = $this->mockDb();
$db->method('get')->willReturnOnConsecutiveCalls(
['id' => 2, 'login' => 'user'], // login istnieje
['id' => 2, 'status' => 1, 'error_logged_count' => 0], // nie zablokowany
['id' => 2] // hasło poprawne
);
$db->method('update')->willReturn(true);
$repo = new UserRepository($db);
$this->assertSame(1, $repo->logon('user', 'pass'));
}
// --- isLoginTaken ---
public function testIsLoginTakenReturnsTrueWhenExists(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn('user');
$repo = new UserRepository($db);
$this->assertTrue($repo->isLoginTaken('user'));
}
public function testIsLoginTakenReturnsFalseWhenFree(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(null);
$repo = new UserRepository($db);
$this->assertFalse($repo->isLoginTaken('newuser'));
}
// --- verifyTwofaCode ---
public function testVerifyTwofaCodeReturnsFalseWhenUserNotFound(): void
{
$db = $this->mockDb();
$db->method('get')->willReturn(null);
$repo = new UserRepository($db);
$this->assertFalse($repo->verifyTwofaCode(1, '123456'));
}
public function testVerifyTwofaCodeReturnsFalseWhenTooManyFailedAttempts(): void
{
$db = $this->mockDb();
$user = [
'id' => 2,
'twofa_failed_attempts' => 5,
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 600),
'twofa_code_hash' => password_hash('123456', PASSWORD_DEFAULT),
];
$db->method('get')->willReturn($user);
$repo = new UserRepository($db);
$this->assertFalse($repo->verifyTwofaCode(2, '123456'));
}
public function testVerifyTwofaCodeReturnsFalseWhenExpired(): void
{
$db = $this->mockDb();
$user = [
'id' => 2,
'twofa_failed_attempts' => 0,
'twofa_expires_at' => date('Y-m-d H:i:s', time() - 1),
'twofa_code_hash' => password_hash('123456', PASSWORD_DEFAULT),
];
$db->method('get')->willReturn($user);
$db->method('update')->willReturn(true);
$repo = new UserRepository($db);
$this->assertFalse($repo->verifyTwofaCode(2, '123456'));
}
public function testVerifyTwofaCodeReturnsTrueOnValidCode(): void
{
$code = '123456';
$db = $this->mockDb();
$user = [
'id' => 2,
'twofa_failed_attempts' => 0,
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 600),
'twofa_code_hash' => password_hash($code, PASSWORD_DEFAULT),
];
// find() wywołuje get() dwa razy (raz przez verifyTwofaCode, raz przez update)
$db->method('get')->willReturn($user);
$db->method('update')->willReturn(true);
$repo = new UserRepository($db);
$this->assertTrue($repo->verifyTwofaCode(2, $code));
}
// --- delete ---
public function testDeleteReturnsTrueOnSuccess(): void
{
$db = $this->mockDb();
$db->method('delete')->willReturn(1);
$repo = new UserRepository($db);
$this->assertTrue($repo->delete(2));
}
// --- save — walidacja ---
public function testSaveReturnsErrorWhenPasswordTooShort(): void
{
$db = $this->mockDb();
$db->method('delete')->willReturn(1);
$repo = new UserRepository($db);
$result = $repo->save(0, 'newuser', 'on', '', '123', '123', 0, []);
$this->assertSame('error', $result['status']);
}
public function testSaveReturnsErrorWhenPasswordsMismatch(): void
{
$db = $this->mockDb();
$db->method('delete')->willReturn(1);
$repo = new UserRepository($db);
$result = $repo->save(0, 'newuser', 'on', '', 'password1', 'password2', 0, []);
$this->assertSame('error', $result['status']);
}
}

51
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,51 @@
<?php
// Medoo ORM — potrzebny do mockowania w testach
require_once __DIR__ . '/../libraries/medoo/medoo.php';
// Stub: \Shared\Cache\CacheHandler — musi być załadowany PRZED autoloaderem
// żeby nie nadpisała go prawdziwa klasa
namespace Shared\Cache {
class CacheHandler
{
private static array $store = [];
public static function reset(): void { self::$store = []; }
public static function fetch(string $key): mixed
{
return self::$store[$key] ?? false;
}
public static function store(string $key, mixed $value, int $ttl = 0): void
{
self::$store[$key] = $value;
}
public static function delete(string $key): void
{
unset(self::$store[$key]);
}
}
}
// Stub: \S — metody statyczne używane przez repozytoria
namespace {
class S
{
public static function delete_cache(): void {}
public static function htacces(): void {}
public static function get_domain(string $domain = ''): ?string { return $domain ?: null; }
public static function send_email(string $to, string $subject, string $body): bool { return true; }
}
// PSR-4 autoloader dla Domain\
// Shared\ jest już obsłużona przez stubę powyżej — pomijamy ją w autoloaderze
spl_autoload_register(function (string $class): void {
if (strncmp($class, 'Domain\\', 7) === 0) {
$rel = substr($class, 7);
$file = __DIR__ . '/../autoload/Domain/' . str_replace('\\', '/', $rel) . '.php';
if (file_exists($file)) { require $file; }
}
});
}