Тесты для AuthorizationForm и кастомного RequiredValidator

В модуль Authserver добавлены хелперы для логирования
Исправлена ошибка в MinecraftAccessKey
Ускорено тестирование всего приложения
This commit is contained in:
ErickSkrauch 2016-08-29 02:17:45 +03:00
parent d37a865e14
commit 9c658f5bd9
9 changed files with 241 additions and 13 deletions

View File

@ -52,7 +52,6 @@ class LoginForm extends ApiForm {
} }
public function validateActivity($attribute) { public function validateActivity($attribute) {
// TODO: проверить, не заблокирован ли аккаунт
if (!$this->hasErrors()) { if (!$this->hasErrors()) {
$account = $this->getAccount(); $account = $this->getAccount();
if ($account->status === Account::STATUS_BANNED) { if ($account->status === Account::STATUS_BANNED) {

View File

@ -1,6 +1,7 @@
<?php <?php
namespace api\modules\authserver; namespace api\modules\authserver;
use Yii;
use yii\base\BootstrapInterface; use yii\base\BootstrapInterface;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
@ -31,4 +32,12 @@ class Module extends \yii\base\Module implements BootstrapInterface {
], false); ], false);
} }
public static function info($message) {
Yii::info($message, 'legacy-authserver');
}
public static function error($message) {
Yii::info($message, 'legacy-authserver');
}
} }

View File

@ -3,9 +3,11 @@ namespace api\modules\authserver\models;
use api\models\authentication\LoginForm; use api\models\authentication\LoginForm;
use api\modules\authserver\exceptions\ForbiddenOperationException; use api\modules\authserver\exceptions\ForbiddenOperationException;
use api\modules\authserver\Module as Authserver;
use api\modules\authserver\validators\RequiredValidator; use api\modules\authserver\validators\RequiredValidator;
use common\helpers\Error as E;
use common\models\Account;
use common\models\MinecraftAccessKey; use common\models\MinecraftAccessKey;
use Yii;
class AuthenticationForm extends Form { class AuthenticationForm extends Form {
@ -26,17 +28,22 @@ class AuthenticationForm extends Form {
public function authenticate() { public function authenticate() {
$this->validate(); $this->validate();
Yii::info("Trying to authenticate user by login = '{$this->username}'.", 'legacy-authentication'); Authserver::info("Trying to authenticate user by login = '{$this->username}'.");
$loginForm = new LoginForm(); $loginForm = $this->createLoginForm();
$loginForm->login = $this->username; $loginForm->login = $this->username;
$loginForm->password = $this->password; $loginForm->password = $this->password;
if (!$loginForm->validate()) { if (!$loginForm->validate()) {
$errors = $loginForm->getFirstErrors(); $errors = $loginForm->getFirstErrors();
if (isset($errors['login'])) { if (isset($errors['login'])) {
Yii::error("Cannot find user by login = '{$this->username}", 'legacy-authentication'); if ($errors['login'] === E::ACCOUNT_BANNED) {
Authserver::error("User with login = '{$this->username}' is banned");
throw new ForbiddenOperationException('This account has been suspended.');
} else {
Authserver::error("Cannot find user by login = '{$this->username}'");
}
} elseif (isset($errors['password'])) { } elseif (isset($errors['password'])) {
Yii::error("User with login = '{$this->username}' passed wrong password.", 'legacy-authentication'); Authserver::error("User with login = '{$this->username}' passed wrong password.");
} }
// На старом сервере авторизации использовалось поле nickname, а не username, так что сохраняем эту логику // На старом сервере авторизации использовалось поле nickname, а не username, так что сохраняем эту логику
@ -45,16 +52,27 @@ class AuthenticationForm extends Form {
$attribute = 'nickname'; $attribute = 'nickname';
} }
// TODO: если аккаунт заблокирован, то возвращалось сообщение return "This account has been suspended."
// TODO: эта логика дублируется с логикой в SignoutForm // TODO: эта логика дублируется с логикой в SignoutForm
throw new ForbiddenOperationException("Invalid credentials. Invalid {$attribute} or password."); throw new ForbiddenOperationException("Invalid credentials. Invalid {$attribute} or password.");
} }
$account = $loginForm->getAccount(); $account = $loginForm->getAccount();
$accessTokenModel = $this->createMinecraftAccessToken($account);
$dataModel = new AuthenticateData($accessTokenModel);
Authserver::info("User with id = {$account->id}, username = '{$account->username}' and email = '{$account->email}' successfully logged in.");
return $dataModel;
}
protected function createMinecraftAccessToken(Account $account) : MinecraftAccessKey {
/** @var MinecraftAccessKey|null $accessTokenModel */ /** @var MinecraftAccessKey|null $accessTokenModel */
$accessTokenModel = MinecraftAccessKey::findOne(['client_token' => $this->clientToken]); $accessTokenModel = MinecraftAccessKey::findOne([
'client_token' => $this->clientToken,
'account_id' => $account->id,
]);
if ($accessTokenModel === null) { if ($accessTokenModel === null) {
$accessTokenModel = new MinecraftAccessKey(); $accessTokenModel = new MinecraftAccessKey();
$accessTokenModel->client_token = $this->clientToken; $accessTokenModel->client_token = $this->clientToken;
@ -64,11 +82,11 @@ class AuthenticationForm extends Form {
$accessTokenModel->refreshPrimaryKeyValue(); $accessTokenModel->refreshPrimaryKeyValue();
} }
$dataModel = new AuthenticateData($accessTokenModel); return $accessTokenModel;
}
Yii::info("User with id = {$account->id}, username = '{$account->username}' and email = '{$account->email}' successfully logged in.", 'legacy-authentication'); protected function createLoginForm() : LoginForm {
return new LoginForm();
return $dataModel;
} }
} }

View File

@ -54,7 +54,7 @@ class MinecraftAccessKey extends ActiveRecord {
} }
public function isActual() : bool { public function isActual() : bool {
return $this->timestamp + self::LIFETIME >= time(); return $this->updated_at + self::LIFETIME >= time();
} }
} }

View File

@ -0,0 +1,147 @@
<?php
namespace codeception\api\unit\modules\authserver\models;
use api\models\AccountIdentity;
use api\models\authentication\LoginForm;
use api\modules\authserver\models\AuthenticateData;
use api\modules\authserver\models\AuthenticationForm;
use common\models\Account;
use common\models\MinecraftAccessKey;
use Ramsey\Uuid\Uuid;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\common\_support\ProtectedCaller;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\MinecraftAccessKeyFixture;
/**
* @property AccountFixture $accounts
* @property MinecraftAccessKeyFixture $minecraftAccessKeys
*/
class AuthenticationFormTest extends DbTestCase {
use ProtectedCaller;
public function fixtures() {
return [
'accounts' => AccountFixture::class,
'minecraftAccessKeys' => MinecraftAccessKeyFixture::class,
];
}
/**
* @expectedException \api\modules\authserver\exceptions\ForbiddenOperationException
* @expectedExceptionMessage Invalid credentials. Invalid nickname or password.
*/
public function testAuthenticateByWrongNicknamePass() {
$authForm = $this->createAuthForm();
$authForm->username = 'wrong-username';
$authForm->password = 'wrong-password';
$authForm->clientToken = Uuid::uuid4();
$authForm->authenticate();
}
/**
* @expectedException \api\modules\authserver\exceptions\ForbiddenOperationException
* @expectedExceptionMessage Invalid credentials. Invalid email or password.
*/
public function testAuthenticateByWrongEmailPass() {
$authForm = $this->createAuthForm();
$authForm->username = 'wrong-email@ely.by';
$authForm->password = 'wrong-password';
$authForm->clientToken = Uuid::uuid4();
$authForm->authenticate();
}
/**
* @expectedException \api\modules\authserver\exceptions\ForbiddenOperationException
* @expectedExceptionMessage This account has been suspended.
*/
public function testAuthenticateByValidCredentialsIntoBlockedAccount() {
$authForm = $this->createAuthForm(Account::STATUS_BANNED);
$authForm->username = 'dummy';
$authForm->password = 'password_0';
$authForm->clientToken = Uuid::uuid4();
$authForm->authenticate();
}
public function testAuthenticateByValidCredentials() {
$authForm = $this->createAuthForm();
$minecraftAccessKey = new MinecraftAccessKey();
$minecraftAccessKey->access_token = Uuid::uuid4();
$authForm->expects($this->once())
->method('createMinecraftAccessToken')
->will($this->returnValue($minecraftAccessKey));
$authForm->username = 'dummy';
$authForm->password = 'password_0';
$authForm->clientToken = Uuid::uuid4();
$result = $authForm->authenticate();
$this->assertInstanceOf(AuthenticateData::class, $result);
$this->assertEquals($minecraftAccessKey->access_token, $result->getMinecraftAccessKey()->access_token);
}
public function testCreateMinecraftAccessToken() {
$authForm = new AuthenticationForm();
$fixturesCount = count($this->minecraftAccessKeys->data);
$authForm->clientToken = Uuid::uuid4();
/** @var Account $account */
$account = $this->accounts->getModel('admin');
/** @var MinecraftAccessKey $result */
$result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account);
$this->assertInstanceOf(MinecraftAccessKey::class, $result);
$this->assertEquals($account->id, $result->account_id);
$this->assertEquals($authForm->clientToken, $result->client_token);
$this->assertEquals($fixturesCount + 1, MinecraftAccessKey::find()->count());
}
public function testCreateMinecraftAccessTokenWithExistsClientId() {
$authForm = new AuthenticationForm();
$fixturesCount = count($this->minecraftAccessKeys->data);
$authForm->clientToken = $this->minecraftAccessKeys[0]['client_token'];
/** @var Account $account */
$account = $this->accounts->getModel('admin');
/** @var MinecraftAccessKey $result */
$result = $this->callProtected($authForm, 'createMinecraftAccessToken', $account);
$this->assertInstanceOf(MinecraftAccessKey::class, $result);
$this->assertEquals($account->id, $result->account_id);
$this->assertEquals($authForm->clientToken, $result->client_token);
$this->assertEquals($fixturesCount, MinecraftAccessKey::find()->count());
}
private function createAuthForm($status = Account::STATUS_ACTIVE) {
/** @var LoginForm|\PHPUnit_Framework_MockObject_MockObject $loginForm */
$loginForm = $this->getMockBuilder(LoginForm::class)
->setMethods(['getAccount'])
->getMock();
$account = new AccountIdentity();
$account->username = 'dummy';
$account->email = 'dummy@ely.by';
$account->status = $status;
$account->setPassword('password_0');
$loginForm->expects($this->any())
->method('getAccount')
->will($this->returnValue($account));
/** @var AuthenticationForm|\PHPUnit_Framework_MockObject_MockObject $authForm */
$authForm = $this->getMockBuilder(AuthenticationForm::class)
->setMethods(['createLoginForm', 'createMinecraftAccessToken'])
->getMock();
$authForm->expects($this->any())
->method('createLoginForm')
->will($this->returnValue($loginForm));
return $authForm;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace codeception\api\unit\modules\authserver\validators;
use api\modules\authserver\validators\RequiredValidator;
use tests\codeception\api\unit\TestCase;
use tests\codeception\common\_support\ProtectedCaller;
class RequiredValidatorTest extends TestCase {
use ProtectedCaller;
public function testValidateValueNormal() {
$validator = new RequiredValidator();
$this->assertNull($this->callProtected($validator, 'validateValue', 'dummy'));
}
/**
* @expectedException \api\modules\authserver\exceptions\IllegalArgumentException
*/
public function testValidateValueEmpty() {
$validator = new RequiredValidator();
$this->assertNull($this->callProtected($validator, 'validateValue', ''));
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace tests\codeception\common\fixtures;
use common\models\MinecraftAccessKey;
use yii\test\ActiveFixture;
class MinecraftAccessKeyFixture extends ActiveFixture {
public $modelClass = MinecraftAccessKey::class;
public $dataFile = '@tests/codeception/common/fixtures/data/minecraft-access-keys.php';
public $depends = [
AccountFixture::class,
];
}

View File

@ -0,0 +1,10 @@
<?php
return [
[
'access_token' => 'e7bb6648-2183-4981-9b86-eba5e7f87b42',
'client_token' => '6f380440-0c05-47bd-b7c6-d011f1b5308f',
'account_id' => 1,
'created_at' => 1472423530,
'updated_at' => 1472423530,
],
];

View File

@ -30,5 +30,9 @@ return [
'password' => 'tester-password', 'password' => 'tester-password',
'vhost' => '/account.ely.by/tests', 'vhost' => '/account.ely.by/tests',
], ],
'security' => [
// Для тестов нам не сильно важна безопасность, а вот время прохождения тестов значительно сокращается
'passwordHashCost' => 4,
],
], ],
]; ];