RBAC and social controller

This commit is contained in:
2026-01-29 17:54:12 +01:00
parent 98a0e33939
commit e003257c84
48 changed files with 4510 additions and 73 deletions

185
tests/unit/ModuleTest.php Normal file
View File

@@ -0,0 +1,185 @@
<?php
declare(strict_types=1);
namespace tests\unit;
use Codeception\Test\Unit;
use cgsmith\user\Module;
class ModuleTest extends Unit
{
private Module $module;
protected function _before(): void
{
$this->module = new Module('user');
}
public function testVersionReturnsString(): void
{
$this->assertIsString($this->module->getVersion());
$this->assertEquals(Module::VERSION, $this->module->getVersion());
}
public function testDefaultModelMapReturnsUserClass(): void
{
$userClass = $this->module->getModelClass('User');
$this->assertEquals('cgsmith\user\models\User', $userClass);
}
public function testDefaultModelMapReturnsProfileClass(): void
{
$profileClass = $this->module->getModelClass('Profile');
$this->assertEquals('cgsmith\user\models\Profile', $profileClass);
}
public function testDefaultModelMapReturnsTokenClass(): void
{
$tokenClass = $this->module->getModelClass('Token');
$this->assertEquals('cgsmith\user\models\Token', $tokenClass);
}
public function testDefaultModelMapReturnsLoginFormClass(): void
{
$loginFormClass = $this->module->getModelClass('LoginForm');
$this->assertEquals('cgsmith\user\models\LoginForm', $loginFormClass);
}
public function testCustomModelMapOverridesDefault(): void
{
$this->module->modelMap = [
'User' => 'app\models\CustomUser',
];
$userClass = $this->module->getModelClass('User');
$this->assertEquals('app\models\CustomUser', $userClass);
}
public function testGetModelClassThrowsExceptionForUnknownModel(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown model: NonExistent');
$this->module->getModelClass('NonExistent');
}
public function testEmailChangeStrategyConstants(): void
{
$this->assertEquals(0, Module::EMAIL_CHANGE_INSECURE);
$this->assertEquals(1, Module::EMAIL_CHANGE_DEFAULT);
$this->assertEquals(2, Module::EMAIL_CHANGE_SECURE);
}
public function testDefaultConfiguration(): void
{
$this->assertTrue($this->module->enableRegistration);
$this->assertTrue($this->module->enableConfirmation);
$this->assertFalse($this->module->enableUnconfirmedLogin);
$this->assertTrue($this->module->enablePasswordRecovery);
$this->assertFalse($this->module->enableGdpr);
$this->assertFalse($this->module->enableTwoFactor);
$this->assertFalse($this->module->enableSocialAuth);
$this->assertFalse($this->module->enableCaptcha);
$this->assertFalse($this->module->enableSessionHistory);
}
public function testDefaultPasswordSettings(): void
{
$this->assertEquals(8, $this->module->minPasswordLength);
$this->assertEquals(72, $this->module->maxPasswordLength);
$this->assertEquals(12, $this->module->cost);
}
public function testDefaultTokenExpiration(): void
{
$this->assertEquals(86400, $this->module->confirmWithin);
$this->assertEquals(21600, $this->module->recoverWithin);
$this->assertEquals(1209600, $this->module->rememberFor);
}
public function testDefaultAvatarSettings(): void
{
$this->assertTrue($this->module->enableGravatar);
$this->assertTrue($this->module->enableAvatarUpload);
$this->assertEquals('@webroot/uploads/avatars', $this->module->avatarPath);
$this->assertEquals('@web/uploads/avatars', $this->module->avatarUrl);
$this->assertEquals(2097152, $this->module->maxAvatarSize);
$this->assertEquals(['jpg', 'jpeg', 'png', 'gif', 'webp'], $this->module->avatarExtensions);
}
public function testDefaultUrlPrefix(): void
{
$this->assertEquals('user', $this->module->urlPrefix);
}
public function testMailerSenderWithDefault(): void
{
$sender = $this->module->getMailerSender();
$this->assertIsArray($sender);
}
public function testMailerSenderWithCustomConfig(): void
{
$this->module->mailer = [
'sender' => ['custom@example.com' => 'Custom Sender'],
];
$sender = $this->module->getMailerSender();
$this->assertEquals(['custom@example.com' => 'Custom Sender'], $sender);
}
public function testDefaultCaptchaSettings(): void
{
$this->assertEquals('yii', $this->module->captchaType);
$this->assertEquals(['register'], $this->module->captchaForms);
$this->assertEquals(0.5, $this->module->reCaptchaV3Threshold);
}
public function testDefaultTwoFactorSettings(): void
{
$this->assertEquals('', $this->module->twoFactorIssuer);
$this->assertEquals(10, $this->module->twoFactorBackupCodesCount);
$this->assertFalse($this->module->twoFactorRequireForAdmins);
}
public function testDefaultGdprSettings(): void
{
$this->assertEquals('1.0', $this->module->gdprConsentVersion);
$this->assertNull($this->module->gdprConsentUrl);
$this->assertEquals([], $this->module->gdprExemptRoutes);
$this->assertTrue($this->module->requireGdprConsentBeforeRegistration);
}
public function testDefaultSessionSettings(): void
{
$this->assertEquals(10, $this->module->sessionHistoryLimit);
$this->assertFalse($this->module->enableSessionSeparation);
$this->assertEquals('BACKENDSESSID', $this->module->backendSessionName);
$this->assertEquals('PHPSESSID', $this->module->frontendSessionName);
}
public function testDefaultSocialAuthSettings(): void
{
$this->assertTrue($this->module->enableSocialRegistration);
$this->assertTrue($this->module->enableSocialConnect);
}
public function testDefaultRbacSettings(): void
{
$this->assertFalse($this->module->enableRbacManagement);
$this->assertNull($this->module->rbacManagementPermission);
$this->assertNull($this->module->adminPermission);
$this->assertNull($this->module->impersonatePermission);
}
public function testDefaultActiveFormClass(): void
{
$this->assertEquals('yii\widgets\ActiveForm', $this->module->activeFormClass);
}
public function testAdminsArrayIsEmpty(): void
{
$this->assertEquals([], $this->module->admins);
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace tests\unit\helpers;
use Codeception\Test\Unit;
use cgsmith\user\helpers\Password;
class PasswordTest extends Unit
{
public function testHashCreatesValidHash(): void
{
$password = 'testPassword123!';
$hash = Password::hash($password);
$this->assertNotEmpty($hash);
$this->assertNotEquals($password, $hash);
$this->assertStringStartsWith('$2y$', $hash);
}
public function testHashWithCustomCost(): void
{
$password = 'testPassword123!';
$hash = Password::hash($password, 10);
$this->assertNotEmpty($hash);
$this->assertTrue(Password::validate($password, $hash));
}
public function testValidateReturnsTrueForCorrectPassword(): void
{
$password = 'testPassword123!';
$hash = Password::hash($password);
$this->assertTrue(Password::validate($password, $hash));
}
public function testValidateReturnsFalseForIncorrectPassword(): void
{
$password = 'testPassword123!';
$wrongPassword = 'wrongPassword456!';
$hash = Password::hash($password);
$this->assertFalse(Password::validate($wrongPassword, $hash));
}
public function testGenerateCreatesPasswordOfSpecifiedLength(): void
{
$length = 16;
$password = Password::generate($length);
$this->assertEquals($length, strlen($password));
}
public function testGenerateDefaultLength(): void
{
$password = Password::generate();
$this->assertEquals(12, strlen($password));
}
public function testGenerateCreatesUniquePasswords(): void
{
$passwords = [];
for ($i = 0; $i < 10; $i++) {
$passwords[] = Password::generate();
}
$uniquePasswords = array_unique($passwords);
$this->assertCount(10, $uniquePasswords);
}
public function testCheckStrengthWeakPassword(): void
{
$result = Password::checkStrength('abc');
$this->assertEquals(0, $result['score']);
$this->assertNotEmpty($result['feedback']);
}
public function testCheckStrengthMediumPassword(): void
{
$result = Password::checkStrength('Password1');
$this->assertGreaterThan(1, $result['score']);
$this->assertLessThanOrEqual(4, $result['score']);
}
public function testCheckStrengthStrongPassword(): void
{
$result = Password::checkStrength('MyStr0ng!Passw0rd');
$this->assertEquals(4, $result['score']);
$this->assertEmpty($result['feedback']);
}
public function testCheckStrengthFeedbackForShortPassword(): void
{
$result = Password::checkStrength('short');
$this->assertContains('Password should be at least 8 characters.', $result['feedback']);
}
public function testCheckStrengthFeedbackForNoUppercase(): void
{
$result = Password::checkStrength('lowercase123!');
$this->assertContains('Add uppercase letters.', $result['feedback']);
}
public function testCheckStrengthFeedbackForNoLowercase(): void
{
$result = Password::checkStrength('UPPERCASE123!');
$this->assertContains('Add lowercase letters.', $result['feedback']);
}
public function testCheckStrengthFeedbackForNoNumbers(): void
{
$result = Password::checkStrength('NoNumbersHere!');
$this->assertContains('Add numbers.', $result['feedback']);
}
public function testCheckStrengthFeedbackForNoSpecialChars(): void
{
$result = Password::checkStrength('NoSpecial123');
$this->assertContains('Add special characters.', $result['feedback']);
}
public function testCheckStrengthMaxScoreIsFour(): void
{
$result = Password::checkStrength('ThisIsAnExtremelyLongAndSecurePassword123!@#');
$this->assertEquals(4, $result['score']);
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace tests\unit\services;
use Codeception\Test\Unit;
use cgsmith\user\Module;
use cgsmith\user\services\CaptchaService;
class CaptchaServiceTest extends Unit
{
private Module $module;
private CaptchaService $service;
protected function _before(): void
{
$this->module = new Module('user');
$this->service = new CaptchaService($this->module);
}
public function testIsEnabledForFormReturnsFalseWhenCaptchaDisabled(): void
{
$this->module->enableCaptcha = false;
$this->assertFalse($this->service->isEnabledForForm('login'));
$this->assertFalse($this->service->isEnabledForForm('register'));
$this->assertFalse($this->service->isEnabledForForm('recovery'));
}
public function testIsEnabledForFormReturnsTrueWhenFormInList(): void
{
$this->module->enableCaptcha = true;
$this->module->captchaForms = ['login', 'register'];
$this->assertTrue($this->service->isEnabledForForm('login'));
$this->assertTrue($this->service->isEnabledForForm('register'));
$this->assertFalse($this->service->isEnabledForForm('recovery'));
}
public function testGetCaptchaTypeReturnsConfiguredType(): void
{
$this->module->captchaType = CaptchaService::TYPE_RECAPTCHA_V2;
$this->assertEquals(CaptchaService::TYPE_RECAPTCHA_V2, $this->service->getCaptchaType());
}
public function testGetSiteKeyReturnsNullForYiiType(): void
{
$this->module->captchaType = CaptchaService::TYPE_YII;
$this->assertNull($this->service->getSiteKey());
}
public function testGetSiteKeyReturnsReCaptchaKey(): void
{
$this->module->captchaType = CaptchaService::TYPE_RECAPTCHA_V2;
$this->module->reCaptchaSiteKey = 'test-site-key';
$this->assertEquals('test-site-key', $this->service->getSiteKey());
}
public function testGetSiteKeyReturnsReCaptchaV3Key(): void
{
$this->module->captchaType = CaptchaService::TYPE_RECAPTCHA_V3;
$this->module->reCaptchaSiteKey = 'test-v3-site-key';
$this->assertEquals('test-v3-site-key', $this->service->getSiteKey());
}
public function testGetSiteKeyReturnsHCaptchaKey(): void
{
$this->module->captchaType = CaptchaService::TYPE_HCAPTCHA;
$this->module->hCaptchaSiteKey = 'hcaptcha-site-key';
$this->assertEquals('hcaptcha-site-key', $this->service->getSiteKey());
}
public function testGetReCaptchaActionReturnsCorrectActions(): void
{
$this->assertEquals('login', $this->service->getReCaptchaAction('login'));
$this->assertEquals('register', $this->service->getReCaptchaAction('register'));
$this->assertEquals('recovery', $this->service->getReCaptchaAction('recovery'));
$this->assertEquals('submit', $this->service->getReCaptchaAction('unknown'));
}
public function testVerifyReCaptchaReturnsFalseWithoutSecretKey(): void
{
$this->module->reCaptchaSecretKey = null;
$this->assertFalse($this->service->verifyReCaptcha('test-response'));
}
public function testVerifyHCaptchaReturnsFalseWithoutSecretKey(): void
{
$this->module->hCaptchaSecretKey = null;
$this->assertFalse($this->service->verifyHCaptcha('test-response'));
}
public function testCaptchaTypeConstants(): void
{
$this->assertEquals('yii', CaptchaService::TYPE_YII);
$this->assertEquals('recaptcha-v2', CaptchaService::TYPE_RECAPTCHA_V2);
$this->assertEquals('recaptcha-v3', CaptchaService::TYPE_RECAPTCHA_V3);
$this->assertEquals('hcaptcha', CaptchaService::TYPE_HCAPTCHA);
}
}