Rework tests structure. Upgrade codeception to 2.5.3. Merge params configuration into app configuration.

This commit is contained in:
ErickSkrauch
2019-02-20 22:58:52 +03:00
parent 2eacc581be
commit b05dc6816e
248 changed files with 1503 additions and 1339 deletions

View File

@@ -0,0 +1,29 @@
<?php
namespace api\tests\unit;
use Mockery;
class TestCase extends \Codeception\Test\Unit {
/**
* @var \api\tests\UnitTester
*/
protected $tester;
protected function tearDown() {
parent::tearDown();
Mockery::close();
}
/**
* Список фикстур, что будут загружены перед тестом, но после зачистки базы данных
*
* @url http://codeception.com/docs/modules/Yii2#fixtures
*
* @return array
*/
public function _fixtures() {
return [];
}
}

View File

@@ -0,0 +1,2 @@
<?php
// Here you can initialize variables that will for your tests

View File

@@ -0,0 +1,74 @@
<?php
namespace codeception\api\unit\components\ReCaptcha;
use api\components\ReCaptcha\Validator;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Psr7\Response;
use phpmock\mockery\PHPMockery;
use ReflectionClass;
use api\tests\unit\TestCase;
class ValidatorTest extends TestCase {
public function testValidateEmptyValue() {
$validator = new Validator(mock(ClientInterface::class));
$this->assertFalse($validator->validate('', $error));
$this->assertEquals('error.captcha_required', $error, 'Get error.captcha_required, if passed empty value');
}
public function testValidateInvalidValue() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andReturn(new Response(200, [], json_encode([
'success' => false,
'error-codes' => [
'invalid-input-response', // The response parameter is invalid or malformed.
],
])));
$validator = new Validator($mockClient);
$this->assertFalse($validator->validate('12341234', $error));
$this->assertEquals('error.captcha_invalid', $error, 'Get error.captcha_invalid, if passed wrong value');
}
public function testValidateWithNetworkTroubles() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andThrow(mock(ConnectException::class))->once();
$mockClient->shouldReceive('request')->andReturn(new Response(200, [], json_encode([
'success' => true,
'error-codes' => [
'invalid-input-response', // The response parameter is invalid or malformed.
],
])))->once();
PHPMockery::mock($this->getClassNamespace(Validator::class), 'sleep')->once();
$validator = new Validator($mockClient);
$this->assertTrue($validator->validate('12341234', $error));
$this->assertNull($error);
}
public function testValidateWithHugeNetworkTroubles() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andThrow(mock(ConnectException::class))->times(3);
PHPMockery::mock($this->getClassNamespace(Validator::class), 'sleep')->times(2);
$validator = new Validator($mockClient);
$this->expectException(ConnectException::class);
$validator->validate('12341234', $error);
}
public function testValidateValidValue() {
$mockClient = mock(ClientInterface::class);
$mockClient->shouldReceive('request')->andReturn(new Response(200, [], json_encode([
'success' => true,
])));
$validator = new Validator($mockClient);
$this->assertTrue($validator->validate('12341234', $error));
$this->assertNull($error);
}
private function getClassNamespace(string $className): string {
return (new ReflectionClass($className))->getNamespaceName();
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace codeception\api\unit\components\User;
use api\components\User\AuthenticationResult;
use api\components\User\Component;
use api\components\User\Identity;
use common\models\Account;
use common\models\AccountSession;
use Emarref\Jwt\Claim;
use Emarref\Jwt\Jwt;
use Emarref\Jwt\Token;
use api\tests\unit\TestCase;
use common\tests\_support\ProtectedCaller;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\AccountSessionFixture;
use common\tests\fixtures\MinecraftAccessKeyFixture;
use Yii;
use yii\web\Request;
class ComponentTest extends TestCase {
use ProtectedCaller;
/**
* @var Component|\PHPUnit_Framework_MockObject_MockObject
*/
private $component;
public function _before() {
parent::_before();
$this->component = new Component($this->getComponentConfig());
}
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'sessions' => AccountSessionFixture::class,
'minecraftSessions' => MinecraftAccessKeyFixture::class,
];
}
public function testCreateJwtAuthenticationToken() {
$this->mockRequest();
$account = new Account(['id' => 1]);
$result = $this->component->createJwtAuthenticationToken($account, false);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertNull($result->getSession());
$this->assertEquals($account, $result->getAccount());
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), '', 3);
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time() + 60 * 60 * 24 * 7, $payloads->findClaimByName('exp')->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('ely|1', $payloads->findClaimByName('sub')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
$this->assertNull($payloads->findClaimByName('jti'));
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$result = $this->component->createJwtAuthenticationToken($account, true);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertInstanceOf(AccountSession::class, $result->getSession());
$this->assertEquals($account, $result->getAccount());
/** @noinspection NullPointerExceptionInspection */
$this->assertTrue($result->getSession()->refresh());
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time() + 3600, $payloads->findClaimByName('exp')->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('ely|1', $payloads->findClaimByName('sub')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals($result->getSession()->id, $payloads->findClaimByName('jti')->getValue());
}
public function testRenewJwtAuthenticationToken() {
$userIP = '192.168.0.1';
$this->mockRequest($userIP);
/** @var AccountSession $session */
$session = $this->tester->grabFixture('sessions', 'admin');
$result = $this->component->renewJwtAuthenticationToken($session);
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertEquals($session, $result->getSession());
$this->assertEquals($session->account_id, $result->getAccount()->id);
$session->refresh(); // reload data from db
$this->assertEquals(time(), $session->last_refreshed_at, '', 3);
$this->assertEquals($userIP, $session->getReadableIp());
$payloads = (new Jwt())->deserialize($result->getJwt())->getPayload();
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time(), $payloads->findClaimByName(Claim\IssuedAt::NAME)->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals(time() + 3600, $payloads->findClaimByName('exp')->getValue(), '', 3);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('ely|1', $payloads->findClaimByName('sub')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals('accounts_web_user', $payloads->findClaimByName('ely-scopes')->getValue());
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals($session->id, $payloads->findClaimByName('jti')->getValue(), 'session has not changed');
}
public function testParseToken() {
$this->mockRequest();
$token = $this->callProtected($this->component, 'createToken', new Account(['id' => 1]));
$jwt = $this->callProtected($this->component, 'serializeToken', $token);
$this->assertInstanceOf(Token::class, $this->component->parseToken($jwt), 'success get RenewResult object');
}
public function testGetActiveSession() {
$account = $this->tester->grabFixture('accounts', 'admin');
$result = $this->component->createJwtAuthenticationToken($account, true);
$this->component->logout();
/** @var Component|\PHPUnit_Framework_MockObject_MockObject $component */
$component = $this->getMockBuilder(Component::class)
->setMethods(['getIsGuest'])
->setConstructorArgs([$this->getComponentConfig()])
->getMock();
$component
->expects($this->any())
->method('getIsGuest')
->willReturn(false);
$this->mockAuthorizationHeader($result->getJwt());
$session = $component->getActiveSession();
$this->assertInstanceOf(AccountSession::class, $session);
/** @noinspection NullPointerExceptionInspection */
$this->assertEquals($session->id, $result->getSession()->id);
}
public function testTerminateSessions() {
/** @var AccountSession $session */
$session = AccountSession::findOne($this->tester->grabFixture('sessions', 'admin2')['id']);
/** @var Component|\Mockery\MockInterface $component */
$component = mock(Component::class . '[getActiveSession]', [$this->getComponentConfig()])->shouldDeferMissing();
$component->shouldReceive('getActiveSession')->times(1)->andReturn($session);
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$component->createJwtAuthenticationToken($account, true);
$component->terminateSessions($account, Component::KEEP_MINECRAFT_SESSIONS | Component::KEEP_SITE_SESSIONS);
$this->assertNotEmpty($account->getMinecraftAccessKeys()->all());
$this->assertNotEmpty($account->getSessions()->all());
$component->terminateSessions($account, Component::KEEP_SITE_SESSIONS);
$this->assertEmpty($account->getMinecraftAccessKeys()->all());
$this->assertNotEmpty($account->getSessions()->all());
$component->terminateSessions($account, Component::KEEP_CURRENT_SESSION);
$sessions = $account->getSessions()->all();
$this->assertEquals(1, count($sessions));
$this->assertTrue($sessions[0]->id === $session->id);
$component->terminateSessions($account);
$this->assertEmpty($account->getSessions()->all());
$this->assertEmpty($account->getMinecraftAccessKeys()->all());
}
private function mockRequest($userIP = '127.0.0.1') {
/** @var Request|\Mockery\MockInterface $request */
$request = mock(Request::class . '[getHostInfo,getUserIP]')->shouldDeferMissing();
$request->shouldReceive('getHostInfo')->andReturn('http://localhost');
$request->shouldReceive('getUserIP')->andReturn($userIP);
Yii::$app->set('request', $request);
}
/**
* @param string $bearerToken
*/
private function mockAuthorizationHeader($bearerToken = null) {
if ($bearerToken !== null) {
$bearerToken = 'Bearer ' . $bearerToken;
}
Yii::$app->request->headers->set('Authorization', $bearerToken);
}
private function getComponentConfig() {
return [
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace api\tests\unit\components\User;
use api\components\User\AuthenticationResult;
use common\models\Account;
use common\models\AccountSession;
use Emarref\Jwt\Algorithm\Hs256;
use Emarref\Jwt\Claim\Expiration;
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
use Emarref\Jwt\Jwt;
use Emarref\Jwt\Token;
use api\tests\unit\TestCase;
class JwtAuthenticationResultTest extends TestCase {
public function testGetAccount() {
$account = new Account();
$account->id = 123;
$model = new AuthenticationResult($account, '', null);
$this->assertEquals($account, $model->getAccount());
}
public function testGetJwt() {
$model = new AuthenticationResult(new Account(), 'mocked jwt', null);
$this->assertEquals('mocked jwt', $model->getJwt());
}
public function testGetSession() {
$model = new AuthenticationResult(new Account(), '', null);
$this->assertNull($model->getSession());
$session = new AccountSession();
$session->id = 321;
$model = new AuthenticationResult(new Account(), '', $session);
$this->assertEquals($session, $model->getSession());
}
public function testGetAsResponse() {
$jwtToken = $this->createJwtToken(time() + 3600);
$model = new AuthenticationResult(new Account(), $jwtToken, null);
$result = $model->getAsResponse();
$this->assertEquals($jwtToken, $result['access_token']);
$this->assertSame(3600, $result['expires_in']);
/** @noinspection SummerTimeUnsafeTimeManipulationInspection */
$jwtToken = $this->createJwtToken(time() + 86400);
$session = new AccountSession();
$session->refresh_token = 'refresh token';
$model = new AuthenticationResult(new Account(), $jwtToken, $session);
$result = $model->getAsResponse();
$this->assertEquals($jwtToken, $result['access_token']);
$this->assertEquals('refresh token', $result['refresh_token']);
$this->assertSame(86400, $result['expires_in']);
}
private function createJwtToken(int $expires): string {
$token = new Token();
$token->addClaim(new Expiration($expires));
return (new Jwt())->serialize($token, EncryptionFactory::create(new Hs256('123')));
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace api\tests\unit\filters;
use api\filters\NginxCache;
use api\tests\unit\TestCase;
use Yii;
use yii\base\Action;
use yii\web\Controller;
use yii\web\HeaderCollection;
use yii\web\Request;
class NginxCacheTest extends TestCase {
public function testAfterAction() {
$this->testAfterActionInternal(3600, 3600);
$this->testAfterActionInternal('@' . (time() + 30), '@' . (time() + 30));
$this->testAfterActionInternal(function() {
return 3000;
}, 3000);
}
private function testAfterActionInternal($ruleConfig, $expected) {
/** @var HeaderCollection|\PHPUnit_Framework_MockObject_MockObject $headers */
$headers = $this->getMockBuilder(HeaderCollection::class)
->setMethods(['set'])
->getMock();
$headers->expects($this->once())
->method('set')
->with('X-Accel-Expires', $expected);
/** @var Request|\PHPUnit_Framework_MockObject_MockObject $request */
$request = $this->getMockBuilder(Request::class)
->setMethods(['getHeaders'])
->getMock();
$request->expects($this->any())
->method('getHeaders')
->willReturn($headers);
Yii::$app->set('response', $request);
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject $controller */
$controller = $this->getMockBuilder(Controller::class)
->setConstructorArgs(['mock', Yii::$app])
->getMock();
$component = new NginxCache([
'rules' => [
'index' => $ruleConfig,
],
]);
$component->afterAction(new Action('index', $controller), '');
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace codeception\api\unit\models;
use api\models\FeedbackForm;
use common\models\Account;
use api\tests\unit\TestCase;
use yii\swiftmailer\Message;
class FeedbackFormTest extends TestCase {
public function testSendMessage() {
$model = new FeedbackForm([
'subject' => 'Тема обращения',
'email' => 'erickskrauch@ely.by',
'message' => 'Привет мир!',
]);
$this->assertTrue($model->sendMessage());
$this->tester->seeEmailIsSent(1, 'message file exists');
}
public function testSendMessageWithEmail() {
/** @var FeedbackForm|\PHPUnit_Framework_MockObject_MockObject $model */
$model = $this->getMockBuilder(FeedbackForm::class)
->setMethods(['getAccount'])
->setConstructorArgs([[
'subject' => 'Тема обращения',
'email' => 'erickskrauch@ely.by',
'message' => 'Привет мир!',
]])
->getMock();
$model
->expects($this->any())
->method('getAccount')
->will($this->returnValue(new Account([
'id' => '123',
'username' => 'Erick',
'email' => 'find-this@email.net',
'created_at' => time() - 86400,
])));
$this->assertTrue($model->sendMessage());
/** @var Message $message */
$message = $this->tester->grabLastSentEmail();
$this->assertInstanceOf(Message::class, $message);
$data = (string)$message;
$this->assertContains('find-this@email.net', $data);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace codeception\api\unit\models;
use api\components\User\IdentityInterface;
use api\components\User\Jwt;
use api\components\User\JwtIdentity;
use Codeception\Specify;
use Emarref\Jwt\Claim;
use Emarref\Jwt\Encryption\Factory as EncryptionFactory;
use Emarref\Jwt\Token;
use api\tests\unit\TestCase;
use common\tests\_support\ProtectedCaller;
use common\tests\fixtures\AccountFixture;
use Yii;
class JwtIdentityTest extends TestCase {
use Specify;
use ProtectedCaller;
public function _fixtures(): array {
return [
'accounts' => AccountFixture::class,
];
}
public function testFindIdentityByAccessToken() {
$token = $this->generateToken();
$identity = JwtIdentity::findIdentityByAccessToken($token);
$this->assertInstanceOf(IdentityInterface::class, $identity);
$this->assertEquals($token, $identity->getId());
$this->assertEquals($this->tester->grabFixture('accounts', 'admin')['id'], $identity->getAccount()->id);
}
/**
* @expectedException \yii\web\UnauthorizedHttpException
* @expectedExceptionMessage Token expired
*/
public function testFindIdentityByAccessTokenWithExpiredToken() {
$token = new Token();
$token->addClaim(new Claim\IssuedAt(1464593193));
$token->addClaim(new Claim\Expiration(1464596793));
$token->addClaim(new Claim\Subject('ely|' . $this->tester->grabFixture('accounts', 'admin')['id']));
$expiredToken = (new Jwt())->serialize($token, EncryptionFactory::create(Yii::$app->user->getAlgorithm()));
JwtIdentity::findIdentityByAccessToken($expiredToken);
}
/**
* @expectedException \yii\web\UnauthorizedHttpException
* @expectedExceptionMessage Incorrect token
*/
public function testFindIdentityByAccessTokenWithEmptyToken() {
JwtIdentity::findIdentityByAccessToken('');
}
protected function generateToken() {
/** @var \api\components\User\Component $component */
$component = Yii::$app->user;
/** @var \common\models\Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$token = $this->callProtected($component, 'createToken', $account);
return $this->callProtected($component, 'serializeToken', $token);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace api\tests\_support\models\authentication;
use api\components\User\AuthenticationResult;
use api\models\authentication\ConfirmEmailForm;
use common\models\Account;
use common\models\AccountSession;
use common\models\EmailActivation;
use api\tests\unit\TestCase;
use common\tests\fixtures\EmailActivationFixture;
class ConfirmEmailFormTest extends TestCase {
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
}
public function testConfirm() {
$fixture = $this->tester->grabFixture('emailActivations', 'freshRegistrationConfirmation');
$model = $this->createModel($fixture['key']);
$result = $model->confirm();
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertInstanceOf(AccountSession::class, $result->getSession(), 'session was generated');
$activationExists = EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists();
$this->assertFalse($activationExists, 'email activation key is not exist');
/** @var Account $account */
$account = Account::findOne($fixture['account_id']);
$this->assertEquals(Account::STATUS_ACTIVE, $account->status, 'user status changed to active');
}
private function createModel($key) {
return new ConfirmEmailForm([
'key' => $key,
]);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace codeception\api\unit\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\ForgotPasswordForm;
use Codeception\Specify;
use common\models\Account;
use common\models\EmailActivation;
use common\tasks\SendPasswordRecoveryEmail;
use GuzzleHttp\ClientInterface;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\EmailActivationFixture;
use Yii;
class ForgotPasswordFormTest extends TestCase {
use Specify;
public function setUp() {
parent::setUp();
Yii::$container->set(ReCaptchaValidator::class, new class(mock(ClientInterface::class)) extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
});
}
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'emailActivations' => EmailActivationFixture::class,
];
}
public function testValidateLogin() {
$model = new ForgotPasswordForm(['login' => 'unexist']);
$model->validateLogin('login');
$this->assertEquals(['error.login_not_exist'], $model->getErrors('login'), 'error.login_not_exist if login is invalid');
$model = new ForgotPasswordForm(['login' => $this->tester->grabFixture('accounts', 'admin')['username']]);
$model->validateLogin('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if login is exists');
}
public function testValidateActivity() {
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'not-activated-account')['username'],
]);
$model->validateActivity('login');
$this->assertEquals(['error.account_not_activated'], $model->getErrors('login'), 'expected error if account is not confirmed');
$model = new ForgotPasswordForm([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
]);
$model->validateLogin('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if login is exists');
}
public function testValidateFrequency() {
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'freshPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
$this->assertEquals(['error.recently_sent_message'], $model->getErrors('login'), 'error.account_not_activated if recently was message');
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => $this->tester->grabFixture('emailActivations', 'oldPasswordRecovery')['key'],
]);
$model->validateFrequency('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if email was sent a long time ago');
$model = $this->createModel([
'login' => $this->tester->grabFixture('accounts', 'admin')['username'],
'key' => 'invalid-key',
]);
$model->validateFrequency('login');
$this->assertEmpty($model->getErrors('login'), 'empty errors if previous confirmation model not founded');
}
public function testForgotPassword() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new ForgotPasswordForm(['login' => $account->username]);
$this->assertTrue($model->forgotPassword(), 'form should be successfully processed');
$activation = $model->getEmailActivation();
$this->assertInstanceOf(EmailActivation::class, $activation, 'getEmailActivation should return valid object instance');
$this->assertTaskCreated($this->tester->grabLastQueuedJob(), $account, $activation);
}
public function testForgotPasswordResend() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'account-with-expired-forgot-password-message');
$model = new ForgotPasswordForm(['login' => $account->username]);
$callTime = time();
$this->assertTrue($model->forgotPassword(), 'form should be successfully processed');
$emailActivation = $model->getEmailActivation();
$this->assertInstanceOf(EmailActivation::class, $emailActivation);
$this->assertGreaterThanOrEqual($callTime, $emailActivation->created_at);
$this->assertTaskCreated($this->tester->grabLastQueuedJob(), $account, $emailActivation);
}
/**
* @param SendPasswordRecoveryEmail $job
* @param Account $account
* @param EmailActivation $activation
*/
private function assertTaskCreated($job, Account $account, EmailActivation $activation) {
$this->assertInstanceOf(SendPasswordRecoveryEmail::class, $job);
$this->assertSame($account->username, $job->username);
$this->assertSame($account->email, $job->email);
$this->assertSame($account->lang, $job->locale);
$this->assertSame($activation->key, $job->code);
$this->assertSame('http://localhost/recover-password/' . $activation->key, $job->link);
}
/**
* @param array $params
* @return ForgotPasswordForm
*/
private function createModel(array $params = []) {
return new class($params) extends ForgotPasswordForm {
public $key;
public function getEmailActivation() {
return EmailActivation::findOne($this->key);
}
};
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace api\tests\_support\models\authentication;
use api\components\User\AuthenticationResult;
use api\models\authentication\LoginForm;
use Codeception\Specify;
use common\models\Account;
use OTPHP\TOTP;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
class LoginFormTest extends TestCase {
use Specify;
private $originalRemoteAddr;
public function setUp() {
$this->originalRemoteAddr = $_SERVER['REMOTE_ADDR'] ?? null;
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
parent::setUp();
}
public function tearDown() {
parent::tearDown();
$_SERVER['REMOTE_ADDR'] = $this->originalRemoteAddr;
}
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
];
}
public function testValidateLogin() {
$this->specify('error.login_not_exist if login not exists', function() {
$model = $this->createModel([
'login' => 'mr-test',
'account' => null,
]);
$model->validateLogin('login');
$this->assertEquals(['error.login_not_exist'], $model->getErrors('login'));
});
$this->specify('no errors if login exists', function() {
$model = $this->createModel([
'login' => 'mr-test',
'account' => new Account(),
]);
$model->validateLogin('login');
$this->assertEmpty($model->getErrors('login'));
});
}
public function testValidatePassword() {
$this->specify('error.password_incorrect if password invalid', function() {
$model = $this->createModel([
'password' => '87654321',
'account' => new Account(['password' => '12345678']),
]);
$model->validatePassword('password');
$this->assertEquals(['error.password_incorrect'], $model->getErrors('password'));
});
$this->specify('no errors if password valid', function() {
$model = $this->createModel([
'password' => '12345678',
'account' => new Account(['password' => '12345678']),
]);
$model->validatePassword('password');
$this->assertEmpty($model->getErrors('password'));
});
}
public function testValidateTotp() {
$account = new Account(['password' => '12345678']);
$account->password = '12345678';
$account->is_otp_enabled = true;
$account->otp_secret = 'AAAA';
$this->specify('error.totp_incorrect if totp invalid', function() use ($account) {
$model = $this->createModel([
'password' => '12345678',
'totp' => '321123',
'account' => $account,
]);
$model->validateTotp('totp');
$this->assertEquals(['error.totp_incorrect'], $model->getErrors('totp'));
});
$totp = TOTP::create($account->otp_secret);
$this->specify('no errors if password valid', function() use ($account, $totp) {
$model = $this->createModel([
'password' => '12345678',
'totp' => $totp->now(),
'account' => $account,
]);
$model->validateTotp('totp');
$this->assertEmpty($model->getErrors('totp'));
});
}
public function testValidateActivity() {
$this->specify('error.account_not_activated if account in not activated state', function() {
$model = $this->createModel([
'account' => new Account(['status' => Account::STATUS_REGISTERED]),
]);
$model->validateActivity('login');
$this->assertEquals(['error.account_not_activated'], $model->getErrors('login'));
});
$this->specify('error.account_banned if account has banned status', function() {
$model = $this->createModel([
'account' => new Account(['status' => Account::STATUS_BANNED]),
]);
$model->validateActivity('login');
$this->assertEquals(['error.account_banned'], $model->getErrors('login'));
});
$this->specify('no errors if account active', function() {
$model = $this->createModel([
'account' => new Account(['status' => Account::STATUS_ACTIVE]),
]);
$model->validateActivity('login');
$this->assertEmpty($model->getErrors('login'));
});
}
public function testLogin() {
$model = $this->createModel([
'login' => 'erickskrauch',
'password' => '12345678',
'account' => new Account([
'username' => 'erickskrauch',
'password' => '12345678',
'status' => Account::STATUS_ACTIVE,
]),
]);
$this->assertInstanceOf(AuthenticationResult::class, $model->login(), 'model should login user');
$this->assertEmpty($model->getErrors(), 'error message should not be set');
}
public function testLoginWithRehashing() {
$model = new LoginForm([
'login' => $this->tester->grabFixture('accounts', 'user-with-old-password-type')['username'],
'password' => '12345678',
]);
$this->assertInstanceOf(AuthenticationResult::class, $model->login());
$this->assertEmpty($model->getErrors());
$this->assertEquals(
Account::PASS_HASH_STRATEGY_YII2,
$model->getAccount()->password_hash_strategy,
'user, that login using account with old pass hash strategy should update it automatically'
);
}
/**
* @param array $params
* @return LoginForm
*/
private function createModel(array $params = []) {
return new class($params) extends LoginForm {
private $_account;
public function setAccount($value) {
$this->_account = $value;
}
public function getAccount(): ?Account {
return $this->_account;
}
};
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace api\tests\_support\models\authentication;
use api\components\User\Component;
use api\components\User\Identity;
use api\models\authentication\LogoutForm;
use Codeception\Specify;
use common\models\AccountSession;
use api\tests\unit\TestCase;
use Yii;
class LogoutFormTest extends TestCase {
use Specify;
public function testValidateLogout() {
$this->specify('No actions if active session is not exists', function() {
$userComp = $this
->getMockBuilder(Component::class)
->setConstructorArgs([$this->getComponentArgs()])
->setMethods(['getActiveSession'])
->getMock();
$userComp
->expects($this->any())
->method('getActiveSession')
->will($this->returnValue(null));
Yii::$app->set('user', $userComp);
$model = new LogoutForm();
expect($model->logout())->true();
});
$this->specify('if active session is presented, then delete should be called', function() {
$session = $this
->getMockBuilder(AccountSession::class)
->setMethods(['delete'])
->getMock();
$session
->expects($this->once())
->method('delete')
->willReturn(true);
$userComp = $this
->getMockBuilder(Component::class)
->setConstructorArgs([$this->getComponentArgs()])
->setMethods(['getActiveSession'])
->getMock();
$userComp
->expects($this->any())
->method('getActiveSession')
->will($this->returnValue($session));
Yii::$app->set('user', $userComp);
$model = new LogoutForm();
$model->logout();
});
}
private function getComponentArgs() {
return [
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace api\tests\_support\models\authentication;
use api\components\User\AuthenticationResult;
use api\models\authentication\RecoverPasswordForm;
use Codeception\Specify;
use common\models\Account;
use common\models\EmailActivation;
use api\tests\unit\TestCase;
use common\tests\fixtures\EmailActivationFixture;
class RecoverPasswordFormTest extends TestCase {
use Specify;
public function _fixtures() {
return [
'emailActivations' => EmailActivationFixture::class,
];
}
public function testRecoverPassword() {
$fixture = $this->tester->grabFixture('emailActivations', 'freshPasswordRecovery');
$model = new RecoverPasswordForm([
'key' => $fixture['key'],
'newPassword' => '12345678',
'newRePassword' => '12345678',
]);
$result = $model->recoverPassword();
$this->assertInstanceOf(AuthenticationResult::class, $result);
$this->assertNull($result->getSession(), 'session was not generated');
$this->assertFalse(EmailActivation::find()->andWhere(['key' => $fixture['key']])->exists());
/** @var Account $account */
$account = Account::findOne($fixture['account_id']);
$this->assertTrue($account->validatePassword('12345678'));
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace codeception\api\unit\models\authentication;
use api\components\User\AuthenticationResult;
use api\models\authentication\RefreshTokenForm;
use Codeception\Specify;
use common\models\AccountSession;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountSessionFixture;
class RefreshTokenFormTest extends TestCase {
use Specify;
public function _fixtures() {
return [
'sessions' => AccountSessionFixture::class,
];
}
public function testValidateRefreshToken() {
$this->specify('error.refresh_token_not_exist if passed token not exists', function() {
/** @var RefreshTokenForm $model */
$model = new class extends RefreshTokenForm {
public function getSession() {
return null;
}
};
$model->validateRefreshToken();
$this->assertEquals(['error.refresh_token_not_exist'], $model->getErrors('refresh_token'));
});
$this->specify('no errors if token exists', function() {
/** @var RefreshTokenForm $model */
$model = new class extends RefreshTokenForm {
public function getSession() {
return new AccountSession();
}
};
$model->validateRefreshToken();
$this->assertEmpty($model->getErrors('refresh_token'));
});
}
public function testRenew() {
$model = new RefreshTokenForm();
$model->refresh_token = $this->tester->grabFixture('sessions', 'admin')['refresh_token'];
$this->assertInstanceOf(AuthenticationResult::class, $model->renew());
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace api\tests\_support\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\RegistrationForm;
use common\models\Account;
use common\models\EmailActivation;
use common\models\UsernameHistory;
use common\tasks\SendRegistrationEmail;
use GuzzleHttp\ClientInterface;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\EmailActivationFixture;
use common\tests\fixtures\UsernameHistoryFixture;
use common\tests\helpers\Mock;
use Yii;
use yii\validators\EmailValidator;
use yii\web\Request;
use const common\LATEST_RULES_VERSION;
class RegistrationFormTest extends TestCase {
public function setUp() {
parent::setUp();
$this->mockRequest();
Yii::$container->set(ReCaptchaValidator::class, new class(mock(ClientInterface::class)) extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
});
}
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'emailActivations' => EmailActivationFixture::class,
'usernameHistory' => UsernameHistoryFixture::class,
];
}
public function testValidatePasswordAndRePasswordMatch() {
$model = new RegistrationForm([
'password' => 'enough-length',
'rePassword' => 'but-mismatch',
]);
$this->assertFalse($model->validate(['rePassword']));
$this->assertSame(['error.rePassword_does_not_match'], $model->getErrors('rePassword'));
$model = new RegistrationForm([
'password' => 'enough-length',
'rePassword' => 'enough-length',
]);
$this->assertTrue($model->validate(['rePassword']));
$this->assertEmpty($model->getErrors('rePassword'));
}
public function testSignup() {
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturnTrue();
$model = new RegistrationForm([
'username' => 'some_username',
'email' => 'some_email@example.com',
'password' => 'some_password',
'rePassword' => 'some_password',
'rulesAgreement' => true,
'lang' => 'ru',
]);
$account = $model->signup();
$this->expectSuccessRegistration($account);
$this->assertEquals('ru', $account->lang, 'lang is set');
}
public function testSignupWithDefaultLanguage() {
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturnTrue();
$model = new RegistrationForm([
'username' => 'some_username',
'email' => 'some_email@example.com',
'password' => 'some_password',
'rePassword' => 'some_password',
'rulesAgreement' => true,
]);
$account = $model->signup();
$this->expectSuccessRegistration($account);
$this->assertEquals('en', $account->lang, 'lang is set');
}
/**
* @param Account|null $account
*/
private function expectSuccessRegistration($account) {
$this->assertInstanceOf(Account::class, $account, 'user should be valid');
$this->assertTrue($account->validatePassword('some_password'), 'password should be correct');
$this->assertNotEmpty($account->uuid, 'uuid is set');
$this->assertNotNull($account->registration_ip, 'registration_ip is set');
$this->assertEquals(LATEST_RULES_VERSION, $account->rules_agreement_version, 'actual rules version is set');
$this->assertTrue(Account::find()->andWhere([
'username' => 'some_username',
'email' => 'some_email@example.com',
])->exists(), 'user model exists in database');
/** @var EmailActivation $activation */
$activation = EmailActivation::find()
->andWhere([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_REGISTRATION_EMAIL_CONFIRMATION,
])
->one();
$this->assertInstanceOf(EmailActivation::class, $activation, 'email activation code exists in database');
$this->assertTrue(UsernameHistory::find()->andWhere([
'username' => $account->username,
'account_id' => $account->id,
'applied_in' => $account->created_at,
])->exists(), 'username history record exists in database');
/** @var SendRegistrationEmail $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(SendRegistrationEmail::class, $job);
$this->assertSame($account->username, $job->username);
$this->assertSame($account->email, $job->email);
$this->assertSame($account->lang, $job->locale);
$this->assertSame($activation->key, $job->code);
$this->assertSame('http://localhost/activation/' . $activation->key, $job->link);
}
private function mockRequest($ip = '88.225.20.236') {
$request = $this->getMockBuilder(Request::class)
->setMethods(['getUserIP'])
->getMock();
$request
->method('getUserIP')
->willReturn($ip);
Yii::$app->set('request', $request);
return $request;
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace api\tests\_support\models\authentication;
use api\components\ReCaptcha\Validator as ReCaptchaValidator;
use api\models\authentication\RepeatAccountActivationForm;
use Codeception\Specify;
use common\models\EmailActivation;
use common\tasks\SendRegistrationEmail;
use GuzzleHttp\ClientInterface;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\EmailActivationFixture;
use Yii;
class RepeatAccountActivationFormTest extends TestCase {
use Specify;
public function setUp() {
parent::setUp();
Yii::$container->set(ReCaptchaValidator::class, new class(mock(ClientInterface::class)) extends ReCaptchaValidator {
public function validateValue($value) {
return null;
}
});
}
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'activations' => EmailActivationFixture::class,
];
}
public function testValidateEmailForAccount() {
$this->specify('error.email_not_found if passed valid email, but it don\'t exists in database', function() {
$model = new RepeatAccountActivationForm(['email' => 'me-is-not@exists.net']);
$model->validateEmailForAccount('email');
expect($model->getErrors('email'))->equals(['error.email_not_found']);
});
$this->specify('error.account_already_activated if passed valid email, but account already activated', function() {
$fixture = $this->tester->grabFixture('accounts', 'admin');
$model = new RepeatAccountActivationForm(['email' => $fixture['email']]);
$model->validateEmailForAccount('email');
expect($model->getErrors('email'))->equals(['error.account_already_activated']);
});
$this->specify('no errors if passed valid email for not activated account', function() {
$fixture = $this->tester->grabFixture('accounts', 'not-activated-account');
$model = new RepeatAccountActivationForm(['email' => $fixture['email']]);
$model->validateEmailForAccount('email');
expect($model->getErrors('email'))->isEmpty();
});
}
public function testValidateExistsActivation() {
$this->specify('error.recently_sent_message if passed email has recently sent message', function() {
$fixture = $this->tester->grabFixture('activations', 'freshRegistrationConfirmation');
$model = $this->createModel(['emailKey' => $fixture['key']]);
$model->validateExistsActivation('email');
expect($model->getErrors('email'))->equals(['error.recently_sent_message']);
});
$this->specify('no errors if passed email has expired activation message', function() {
$fixture = $this->tester->grabFixture('activations', 'oldRegistrationConfirmation');
$model = $this->createModel(['emailKey' => $fixture['key']]);
$model->validateExistsActivation('email');
expect($model->getErrors('email'))->isEmpty();
});
}
public function testSendRepeatMessage() {
$model = new RepeatAccountActivationForm();
$this->assertFalse($model->sendRepeatMessage(), 'no magic if we don\'t pass validation');
$this->assertEmpty($this->tester->grabQueueJobs());
/** @var \common\models\Account $account */
$account = $this->tester->grabFixture('accounts', 'not-activated-account-with-expired-message');
$model = new RepeatAccountActivationForm(['email' => $account->email]);
$this->assertTrue($model->sendRepeatMessage());
$activation = $model->getActivation();
$this->assertNotNull($activation);
/** @var SendRegistrationEmail $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(SendRegistrationEmail::class, $job);
$this->assertSame($account->username, $job->username);
$this->assertSame($account->email, $job->email);
$this->assertSame($account->lang, $job->locale);
$this->assertSame($activation->key, $job->code);
$this->assertSame('http://localhost/activation/' . $activation->key, $job->link);
}
/**
* @param array $params
* @return RepeatAccountActivationForm
*/
private function createModel(array $params = []) {
return new class($params) extends RepeatAccountActivationForm {
public $emailKey;
public function getActivation() {
return EmailActivation::findOne($this->emailKey);
}
};
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace api\tests\_support\models\base;
use api\models\base\ApiForm;
use api\tests\unit\TestCase;
class ApiFormTest extends TestCase {
public function testLoad() {
$model = new DummyApiForm();
$this->assertTrue($model->load(['field' => 'test-data']), 'model successful load data without prefix');
$this->assertEquals('test-data', $model->field, 'field is set as passed data');
}
}
class DummyApiForm extends ApiForm {
public $field;
public function rules() {
return [
['field', 'safe'],
];
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\AcceptRulesForm;
use common\models\Account;
use api\tests\unit\TestCase;
use const common\LATEST_RULES_VERSION;
class AcceptRulesFormTest extends TestCase {
public function testAgreeWithLatestRules() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->rules_agreement_version = LATEST_RULES_VERSION - 1;
$model = new AcceptRulesForm($account);
$this->assertTrue($model->performAction());
$this->assertEquals(LATEST_RULES_VERSION, $account->rules_agreement_version);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\ChangeEmailForm;
use common\models\Account;
use common\models\EmailActivation;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\EmailActivationFixture;
class ChangeEmailFormTest extends TestCase {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'emailActivations' => EmailActivationFixture::class,
];
}
public function testChangeEmail() {
/** @var Account $account */
$account = Account::findOne($this->getAccountId());
$newEmailConfirmationFixture = $this->tester->grabFixture('emailActivations', 'newEmailConfirmation');
$model = new ChangeEmailForm($account, [
'key' => $newEmailConfirmationFixture['key'],
]);
$this->assertTrue($model->performAction());
$this->assertNull(EmailActivation::findOne([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION,
]));
/** @noinspection UnserializeExploitsInspection */
$data = unserialize($newEmailConfirmationFixture['_data']);
$this->assertEquals($data['newEmail'], $account->email);
}
private function getAccountId() {
return $this->tester->grabFixture('accounts', 'account-with-change-email-finish-state')['id'];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\ChangeLanguageForm;
use common\models\Account;
use api\tests\unit\TestCase;
class ChangeLanguageFormTest extends TestCase {
public function testApplyLanguage() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$model = new ChangeLanguageForm($account);
$model->lang = 'ru';
$this->assertTrue($model->performAction());
$this->assertEquals('ru', $account->lang);
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\components\User\Component;
use api\components\User\Identity;
use api\modules\accounts\models\ChangePasswordForm;
use common\components\UserPass;
use common\helpers\Error as E;
use common\models\Account;
use api\tests\unit\TestCase;
use Yii;
use yii\db\Transaction;
class ChangePasswordFormTest extends TestCase {
public function testValidatePasswordAndRePasswordMatch() {
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'another-password',
]);
$model->validatePasswordAndRePasswordMatch('newRePassword');
$this->assertEquals(
[E::NEW_RE_PASSWORD_DOES_NOT_MATCH],
$model->getErrors('newRePassword'),
'error.rePassword_does_not_match expected if passwords not match'
);
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$model->validatePasswordAndRePasswordMatch('newRePassword');
$this->assertEmpty($model->getErrors('newRePassword'), 'no errors expected if passwords are valid');
// this is very important, because password change flow may be combined of two steps
// therefore we need to validate password sameness before we will validate current account password
$account = new Account();
$account->setPassword('12345678');
$model = new ChangePasswordForm($account, [
'newPassword' => 'my-new-password',
'newRePassword' => 'another-password',
]);
$model->validate();
$this->assertEquals(
[E::NEW_RE_PASSWORD_DOES_NOT_MATCH],
$model->getErrors('newRePassword'),
'error.rePassword_does_not_match expected even if there are errors on other attributes'
);
$this->assertEmpty($model->getErrors('password'));
}
public function testPerformAction() {
$component = mock(Component::class . '[terminateSessions]', [[
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]]);
$component->shouldNotReceive('terminateSessions');
Yii::$app->set('user', $component);
$transaction = mock(Transaction::class . '[commit]');
$transaction->shouldReceive('commit');
$connection = mock(Yii::$app->db);
$connection->shouldReceive('beginTransaction')->andReturn($transaction);
Yii::$app->set('db', $connection);
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->setPassword('password_0');
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$callTime = time();
$this->assertTrue($model->performAction(), 'successfully change password with modern hash strategy');
$this->assertTrue($account->validatePassword('my-new-password'), 'new password should be successfully stored into account');
$this->assertGreaterThanOrEqual($callTime, $account->password_changed_at, 'password change time updated');
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->email = 'mock@ely.by';
$account->password_hash_strategy = Account::PASS_HASH_STRATEGY_OLD_ELY;
$account->password_hash = UserPass::make($account->email, '12345678');
$model = new ChangePasswordForm($account, [
'password' => '12345678',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
]);
$callTime = time();
$this->assertTrue($model->performAction(), 'successfully change password with legacy hash strategy');
$this->assertTrue($account->validatePassword('my-new-password'));
$this->assertGreaterThanOrEqual($callTime, $account->password_changed_at);
$this->assertEquals(Account::PASS_HASH_STRATEGY_YII2, $account->password_hash_strategy);
}
public function testPerformActionWithLogout() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->setPassword('password_0');
/** @var Component|\Mockery\MockInterface $component */
$component = mock(Component::class . '[terminateSessions]', [[
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]]);
$component->shouldReceive('terminateSessions')->once()->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
Yii::$app->set('user', $component);
$model = new ChangePasswordForm($account, [
'password' => 'password_0',
'newPassword' => 'my-new-password',
'newRePassword' => 'my-new-password',
'logoutAll' => true,
]);
$this->assertTrue($model->performAction());
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\ChangeUsernameForm;
use common\models\Account;
use common\models\UsernameHistory;
use common\tasks\PullMojangUsername;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\UsernameHistoryFixture;
class ChangeUsernameFormTest extends TestCase {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'history' => UsernameHistoryFixture::class,
];
}
public function testPerformAction() {
$model = new ChangeUsernameForm($this->getAccount(), [
'password' => 'password_0',
'username' => 'my_new_nickname',
]);
$this->assertTrue($model->performAction());
$this->assertEquals('my_new_nickname', Account::findOne($this->getAccountId())->username);
$this->assertInstanceOf(UsernameHistory::class, UsernameHistory::findOne(['username' => 'my_new_nickname']));
/** @var PullMojangUsername $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(PullMojangUsername::class, $job);
$this->assertSame($job->username, 'my_new_nickname');
}
public function testPerformActionWithTheSameUsername() {
$account = $this->getAccount();
$username = $account->username;
$model = new ChangeUsernameForm($account, [
'password' => 'password_0',
'username' => $username,
]);
$callTime = time();
$this->assertTrue($model->performAction());
$this->assertNull(UsernameHistory::findOne([
'AND',
'username' => $username,
['>=', 'applied_in', $callTime],
]), 'no new UsernameHistory record, if we don\'t change username');
$this->assertNull($this->tester->grabLastQueuedJob());
}
public function testPerformActionWithChangeCase() {
$newUsername = mb_strtoupper($this->tester->grabFixture('accounts', 'admin')['username']);
$model = new ChangeUsernameForm($this->getAccount(), [
'password' => 'password_0',
'username' => $newUsername,
]);
$this->assertTrue($model->performAction());
$this->assertEquals($newUsername, Account::findOne($this->getAccountId())->username);
$this->assertInstanceOf(
UsernameHistory::class,
UsernameHistory::findOne(['username' => $newUsername]),
'username should change, if we change case of some letters'
);
/** @var PullMojangUsername $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(PullMojangUsername::class, $job);
$this->assertSame($job->username, $newUsername);
}
private function getAccount(): Account {
return $this->tester->grabFixture('accounts', 'admin');
}
private function getAccountId() {
return $this->getAccount()->id;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\DisableTwoFactorAuthForm;
use common\helpers\Error as E;
use common\models\Account;
use api\tests\unit\TestCase;
class DisableTwoFactorAuthFormTest extends TestCase {
public function testPerformAction() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class)->makePartial();
$account->shouldReceive('save')->once()->andReturn(true);
$account->is_otp_enabled = true;
$account->otp_secret = 'mock secret';
/** @var DisableTwoFactorAuthForm|\Mockery\MockInterface $model */
$model = mock(DisableTwoFactorAuthForm::class . '[validate]', [$account]);
$model->shouldReceive('validate')->once()->andReturn(true);
$this->assertTrue($model->performAction());
$this->assertNull($account->otp_secret);
$this->assertFalse($account->is_otp_enabled);
}
public function testValidateOtpEnabled() {
$account = new Account();
$account->is_otp_enabled = false;
$model = new DisableTwoFactorAuthForm($account);
$model->validateOtpEnabled('account');
$this->assertEquals([E::OTP_NOT_ENABLED], $model->getErrors('account'));
$account = new Account();
$account->is_otp_enabled = true;
$model = new DisableTwoFactorAuthForm($account);
$model->validateOtpEnabled('account');
$this->assertEmpty($model->getErrors('account'));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\components\User\Component;
use api\components\User\Identity;
use api\modules\accounts\models\EnableTwoFactorAuthForm;
use common\helpers\Error as E;
use common\models\Account;
use api\tests\unit\TestCase;
use Yii;
class EnableTwoFactorAuthFormTest extends TestCase {
public function testPerformAction() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->is_otp_enabled = false;
$account->otp_secret = 'mock secret';
/** @var Component|\Mockery\MockInterface $component */
$component = mock(Component::class . '[terminateSessions]', [[
'identityClass' => Identity::class,
'enableSession' => false,
'loginUrl' => null,
'secret' => 'secret',
]]);
$component->shouldReceive('terminateSessions')->withArgs([$account, Component::KEEP_CURRENT_SESSION]);
Yii::$app->set('user', $component);
/** @var EnableTwoFactorAuthForm|\Mockery\MockInterface $model */
$model = mock(EnableTwoFactorAuthForm::class . '[validate]', [$account]);
$model->shouldReceive('validate')->andReturn(true);
$this->assertTrue($model->performAction());
$this->assertTrue($account->is_otp_enabled);
}
public function testValidateOtpDisabled() {
$account = new Account();
$account->is_otp_enabled = true;
$model = new EnableTwoFactorAuthForm($account);
$model->validateOtpDisabled('account');
$this->assertEquals([E::OTP_ALREADY_ENABLED], $model->getErrors('account'));
$account = new Account();
$account->is_otp_enabled = false;
$model = new EnableTwoFactorAuthForm($account);
$model->validateOtpDisabled('account');
$this->assertEmpty($model->getErrors('account'));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\SendEmailVerificationForm;
use common\models\Account;
use common\models\confirmations\CurrentEmailConfirmation;
use common\models\EmailActivation;
use common\tasks\SendCurrentEmailConfirmation;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\EmailActivationFixture;
class SendEmailVerificationFormTest extends TestCase {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'emailActivations' => EmailActivationFixture::class,
];
}
public function testCreateCode() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new SendEmailVerificationForm($account);
$activationModel = $model->createCode();
$this->assertInstanceOf(CurrentEmailConfirmation::class, $activationModel);
$this->assertEquals($account->id, $activationModel->account_id);
$this->assertNotNull(EmailActivation::findOne($activationModel->key));
}
public function testSendCurrentEmailConfirmation() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new SendEmailVerificationForm($account, [
'password' => 'password_0',
]);
$this->assertTrue($model->performAction());
/** @var EmailActivation $activation */
$activation = EmailActivation::findOne([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_CURRENT_EMAIL_CONFIRMATION,
]);
$this->assertInstanceOf(EmailActivation::class, $activation);
/** @var SendCurrentEmailConfirmation $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(SendCurrentEmailConfirmation::class, $job);
$this->assertSame($account->username, $job->username);
$this->assertSame($account->email, $job->email);
$this->assertSame($activation->key, $job->code);
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\SendNewEmailVerificationForm;
use common\models\Account;
use common\models\confirmations\NewEmailConfirmation;
use common\models\EmailActivation;
use common\tasks\SendNewEmailConfirmation;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\EmailActivationFixture;
use common\tests\helpers\Mock;
use yii\validators\EmailValidator;
class SendNewEmailVerificationFormTest extends TestCase {
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
'emailActivations' => EmailActivationFixture::class,
];
}
public function testCreateCode() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'admin');
$model = new SendNewEmailVerificationForm($account);
$model->email = 'my-new-email@ely.by';
$activationModel = $model->createCode();
$this->assertInstanceOf(NewEmailConfirmation::class, $activationModel);
$this->assertEquals($account->id, $activationModel->account_id);
$this->assertEquals($model->email, $activationModel->newEmail);
$this->assertNotNull(EmailActivation::findOne($activationModel->key));
}
public function testSendNewEmailConfirmation() {
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', 'account-with-change-email-init-state');
/** @var SendNewEmailVerificationForm $model */
$key = $this->tester->grabFixture('emailActivations', 'currentChangeEmailConfirmation')['key'];
$model = new SendNewEmailVerificationForm($account, [
'key' => $key,
'email' => 'my-new-email@ely.by',
]);
Mock::func(EmailValidator::class, 'checkdnsrr')->andReturn(true);
$this->assertTrue($model->performAction());
$this->assertNull(EmailActivation::findOne($key));
/** @var EmailActivation $activation */
$activation = EmailActivation::findOne([
'account_id' => $account->id,
'type' => EmailActivation::TYPE_NEW_EMAIL_CONFIRMATION,
]);
$this->assertNotNull(EmailActivation::class, $activation);
/** @var SendNewEmailConfirmation $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(SendNewEmailConfirmation::class, $job);
$this->assertSame($account->username, $job->username);
$this->assertSame('my-new-email@ely.by', $job->email);
$this->assertSame($activation->key, $job->code);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace api\tests\unit\modules\accounts\models;
use api\modules\accounts\models\TwoFactorAuthInfo;
use common\models\Account;
use api\tests\unit\TestCase;
class TwoFactorAuthInfoTest extends TestCase {
public function testGetCredentials() {
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->email = 'mock@email.com';
$account->otp_secret = null;
$model = new TwoFactorAuthInfo($account);
$result = $model->getCredentials();
$this->assertTrue(is_array($result));
$this->assertArrayHasKey('qr', $result);
$this->assertArrayHasKey('uri', $result);
$this->assertArrayHasKey('secret', $result);
$this->assertSame($account->otp_secret, $result['secret']);
$this->assertSame(strtoupper($account->otp_secret), $account->otp_secret);
$this->assertStringStartsWith('data:image/svg+xml,<?xml', $result['qr']);
$previous = libxml_use_internal_errors(true);
simplexml_load_string(base64_decode($result['qr']));
libxml_use_internal_errors($previous);
$this->assertEmpty(libxml_get_errors());
/** @var Account|\Mockery\MockInterface $account */
$account = mock(Account::class . '[save]');
$account->shouldReceive('save')->andReturn(true);
$account->email = 'mock@email.com';
$account->otp_secret = 'AAAA';
$model = new TwoFactorAuthInfo($account);
$result = $model->getCredentials();
$this->assertEquals('AAAA', $result['secret']);
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace codeception\api\unit\modules\authserver\models;
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 api\tests\unit\TestCase;
use common\tests\_support\ProtectedCaller;
use common\tests\fixtures\AccountFixture;
use common\tests\fixtures\MinecraftAccessKeyFixture;
class AuthenticationFormTest extends TestCase {
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();
$authForm->clientToken = Uuid::uuid4();
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', '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->assertInstanceOf(MinecraftAccessKey::class, MinecraftAccessKey::findOne($result->access_token));
}
public function testCreateMinecraftAccessTokenWithExistsClientId() {
$authForm = new AuthenticationForm();
$minecraftFixture = $this->tester->grabFixture('minecraftAccessKeys', 'admin-token');
$authForm->clientToken = $minecraftFixture['client_token'];
/** @var Account $account */
$account = $this->tester->grabFixture('accounts', '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->assertNull(MinecraftAccessKey::findOne($minecraftFixture['access_token']));
$this->assertInstanceOf(MinecraftAccessKey::class, MinecraftAccessKey::findOne($result->access_token));
}
private function createAuthForm($status = Account::STATUS_ACTIVE) {
/** @var LoginForm|\PHPUnit_Framework_MockObject_MockObject $loginForm */
$loginForm = $this->getMockBuilder(LoginForm::class)
->setMethods(['getAccount'])
->getMock();
$account = new Account();
$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 api\tests\unit\TestCase;
use common\tests\_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,45 @@
<?php
namespace api\tests\unit\modules\internal\models;
use api\modules\accounts\models\BanAccountForm;
use api\modules\internal\helpers\Error as E;
use common\models\Account;
use common\tasks\ClearAccountSessions;
use api\tests\unit\TestCase;
class BanFormTest extends TestCase {
public function testValidateAccountActivity() {
$account = new Account();
$account->status = Account::STATUS_ACTIVE;
$form = new BanAccountForm($account);
$form->validateAccountActivity();
$this->assertEmpty($form->getErrors('account'));
$account = new Account();
$account->status = Account::STATUS_BANNED;
$form = new BanAccountForm($account);
$form->validateAccountActivity();
$this->assertEquals([E::ACCOUNT_ALREADY_BANNED], $form->getErrors('account'));
}
public function testBan() {
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->once())
->method('save')
->willReturn(true);
$model = new BanAccountForm($account);
$this->assertTrue($model->performAction());
$this->assertEquals(Account::STATUS_BANNED, $account->status);
/** @var ClearAccountSessions $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(ClearAccountSessions::class, $job);
$this->assertSame($job->accountId, $account->id);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace api\tests\unit\modules\internal\models;
use api\modules\accounts\models\PardonAccountForm;
use api\modules\internal\helpers\Error as E;
use common\models\Account;
use api\tests\unit\TestCase;
class PardonFormTest extends TestCase {
public function testValidateAccountBanned() {
$account = new Account();
$account->status = Account::STATUS_BANNED;
$form = new PardonAccountForm($account);
$form->validateAccountBanned();
$this->assertEmpty($form->getErrors('account'));
$account = new Account();
$account->status = Account::STATUS_ACTIVE;
$form = new PardonAccountForm($account);
$form->validateAccountBanned();
$this->assertEquals([E::ACCOUNT_NOT_BANNED], $form->getErrors('account'));
}
public function testPardon() {
/** @var Account|\PHPUnit_Framework_MockObject_MockObject $account */
$account = $this->getMockBuilder(Account::class)
->setMethods(['save'])
->getMock();
$account->expects($this->once())
->method('save')
->willReturn(true);
$account->status = Account::STATUS_BANNED;
$model = new PardonAccountForm($account);
$this->assertTrue($model->performAction());
$this->assertEquals(Account::STATUS_ACTIVE, $account->status);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace api\tests\unit\modules\oauth\models;
use api\modules\oauth\models\ApplicationType;
use common\models\OauthClient;
use api\tests\unit\TestCase;
class ApplicationTypeTest extends TestCase {
public function testApplyToClient(): void {
$model = new ApplicationType();
$model->name = 'Application name';
$model->websiteUrl = 'http://example.com';
$model->redirectUri = 'http://example.com/oauth/ely';
$model->description = 'Application description.';
$client = new OauthClient();
$model->applyToClient($client);
$this->assertSame('Application name', $client->name);
$this->assertSame('Application description.', $client->description);
$this->assertSame('http://example.com/oauth/ely', $client->redirect_uri);
$this->assertSame('http://example.com', $client->website_url);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace api\tests\unit\modules\oauth\models;
use api\modules\oauth\models\BaseOauthClientType;
use common\models\OauthClient;
use api\tests\unit\TestCase;
class BaseOauthClientTypeTest extends TestCase {
public function testApplyTyClient(): void {
$client = new OauthClient();
/** @var BaseOauthClientType|\Mockery\MockInterface $form */
$form = mock(BaseOauthClientType::class);
$form->makePartial();
$form->name = 'Application name';
$form->websiteUrl = 'http://example.com';
$form->applyToClient($client);
$this->assertSame('Application name', $client->name);
$this->assertSame('http://example.com', $client->website_url);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace api\tests\unit\modules\oauth\models;
use api\modules\oauth\models\MinecraftServerType;
use common\models\OauthClient;
use api\tests\unit\TestCase;
class MinecraftServerTypeTest extends TestCase {
public function testApplyToClient(): void {
$model = new MinecraftServerType();
$model->name = 'Server name';
$model->websiteUrl = 'http://example.com';
$model->minecraftServerIp = 'localhost:12345';
$client = new OauthClient();
$model->applyToClient($client);
$this->assertSame('Server name', $client->name);
$this->assertSame('http://example.com', $client->website_url);
$this->assertSame('localhost:12345', $client->minecraft_server_ip);
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace api\tests\unit\modules\oauth\models;
use api\modules\oauth\models\ApplicationType;
use api\modules\oauth\models\MinecraftServerType;
use api\modules\oauth\models\OauthClientFormFactory;
use common\models\OauthClient;
use api\tests\unit\TestCase;
class OauthClientFormFactoryTest extends TestCase {
public function testCreate() {
$client = new OauthClient();
$client->type = OauthClient::TYPE_APPLICATION;
$client->name = 'Application name';
$client->description = 'Application description.';
$client->website_url = 'http://example.com';
$client->redirect_uri = 'http://example.com/oauth/ely';
/** @var ApplicationType $requestForm */
$requestForm = OauthClientFormFactory::create($client);
$this->assertInstanceOf(ApplicationType::class, $requestForm);
$this->assertSame('Application name', $requestForm->name);
$this->assertSame('Application description.', $requestForm->description);
$this->assertSame('http://example.com', $requestForm->websiteUrl);
$this->assertSame('http://example.com/oauth/ely', $requestForm->redirectUri);
$client = new OauthClient();
$client->type = OauthClient::TYPE_MINECRAFT_SERVER;
$client->name = 'Server name';
$client->website_url = 'http://example.com';
$client->minecraft_server_ip = 'localhost:12345';
/** @var MinecraftServerType $requestForm */
$requestForm = OauthClientFormFactory::create($client);
$this->assertInstanceOf(MinecraftServerType::class, $requestForm);
$this->assertSame('Server name', $requestForm->name);
$this->assertSame('http://example.com', $requestForm->websiteUrl);
$this->assertSame('localhost:12345', $requestForm->minecraftServerIp);
}
/**
* @expectedException \api\modules\oauth\exceptions\UnsupportedOauthClientType
*/
public function testCreateUnknownType() {
$client = new OauthClient();
$client->type = 'unknown-type';
OauthClientFormFactory::create($client);
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace api\tests\unit\modules\oauth\models;
use api\modules\oauth\models\OauthClientForm;
use api\modules\oauth\models\OauthClientTypeForm;
use common\models\OauthClient;
use common\tasks\ClearOauthSessions;
use api\tests\unit\TestCase;
class OauthClientFormTest extends TestCase {
public function testSave() {
/** @var OauthClient|\Mockery\MockInterface $client */
$client = mock(OauthClient::class . '[save]');
$client->shouldReceive('save')->andReturn(true);
$client->account_id = 1;
$client->type = OauthClient::TYPE_APPLICATION;
$client->name = 'Test application';
/** @var OauthClientForm|\Mockery\MockInterface $form */
$form = mock(OauthClientForm::class . '[isClientExists]', [$client]);
$form->shouldAllowMockingProtectedMethods();
$form->shouldReceive('isClientExists')
->times(3)
->andReturnValues([true, true, false]);
/** @var OauthClientTypeForm|\Mockery\MockInterface $requestType */
$requestType = mock(OauthClientTypeForm::class);
$requestType->shouldReceive('validate')->once()->andReturn(true);
$requestType->shouldReceive('applyToClient')->once()->withArgs([$client]);
$this->assertTrue($form->save($requestType));
$this->assertSame('test-application2', $client->id);
$this->assertNotNull($client->secret);
$this->assertSame(64, mb_strlen($client->secret));
}
public function testSaveUpdateExistsModel() {
/** @var OauthClient|\Mockery\MockInterface $client */
$client = mock(OauthClient::class . '[save]');
$client->shouldReceive('save')->andReturn(true);
$client->setIsNewRecord(false);
$client->id = 'application-id';
$client->secret = 'application_secret';
$client->account_id = 1;
$client->type = OauthClient::TYPE_APPLICATION;
$client->name = 'Application name';
$client->description = 'Application description';
$client->redirect_uri = 'http://example.com/oauth/ely';
$client->website_url = 'http://example.com';
/** @var OauthClientForm|\Mockery\MockInterface $form */
$form = mock(OauthClientForm::class . '[isClientExists]', [$client]);
$form->shouldAllowMockingProtectedMethods();
$form->shouldReceive('isClientExists')->andReturn(false);
$request = new class implements OauthClientTypeForm {
public function load($data): bool {
return true;
}
public function validate(): bool {
return true;
}
public function getValidationErrors(): array {
return [];
}
public function applyToClient(OauthClient $client): void {
$client->name = 'New name';
$client->description = 'New description.';
}
};
$this->assertTrue($form->save($request));
$this->assertSame('application-id', $client->id);
$this->assertSame('application_secret', $client->secret);
$this->assertSame('New name', $client->name);
$this->assertSame('New description.', $client->description);
$this->assertSame('http://example.com/oauth/ely', $client->redirect_uri);
$this->assertSame('http://example.com', $client->website_url);
}
public function testDelete() {
/** @var OauthClient|\Mockery\MockInterface $client */
$client = mock(OauthClient::class . '[save]');
$client->id = 'mocked-id';
$client->type = OauthClient::TYPE_APPLICATION;
$client->shouldReceive('save')->andReturn(true);
$form = new OauthClientForm($client);
$this->assertTrue($form->delete());
$this->assertTrue($form->getClient()->is_deleted);
/** @var ClearOauthSessions $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(ClearOauthSessions::class, $job);
$this->assertSame('mocked-id', $job->clientId);
$this->assertNull($job->notSince);
}
public function testReset() {
/** @var OauthClient|\Mockery\MockInterface $client */
$client = mock(OauthClient::class . '[save]');
$client->id = 'mocked-id';
$client->secret = 'initial_secret';
$client->type = OauthClient::TYPE_APPLICATION;
$client->shouldReceive('save')->andReturn(true);
$form = new OauthClientForm($client);
$this->assertTrue($form->reset());
$this->assertSame('initial_secret', $form->getClient()->secret);
/** @var ClearOauthSessions $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(ClearOauthSessions::class, $job);
$this->assertSame('mocked-id', $job->clientId);
$this->assertEquals(time(), $job->notSince, '', 2);
}
public function testResetWithSecret() {
/** @var OauthClient|\Mockery\MockInterface $client */
$client = mock(OauthClient::class . '[save]');
$client->id = 'mocked-id';
$client->secret = 'initial_secret';
$client->type = OauthClient::TYPE_APPLICATION;
$client->shouldReceive('save')->andReturn(true);
$form = new OauthClientForm($client);
$this->assertTrue($form->reset(true));
$this->assertNotSame('initial_secret', $form->getClient()->secret);
/** @var ClearOauthSessions $job */
$job = $this->tester->grabLastQueuedJob();
$this->assertInstanceOf(ClearOauthSessions::class, $job);
$this->assertSame('mocked-id', $job->clientId);
$this->assertEquals(time(), $job->notSince, '', 2);
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace api\tests\unit\modules\session\filters;
use api\modules\session\filters\RateLimiter;
use common\models\OauthClient;
use Faker\Provider\Internet;
use api\tests\unit\TestCase;
use Yii;
use yii\redis\Connection;
use yii\web\Request;
class RateLimiterTest extends TestCase {
public function testCheckRateLimiterWithOldAuthserver() {
/** @var Connection|\PHPUnit\Framework\MockObject\MockObject $redis */
$redis = $this->getMockBuilder(Connection::class)
->setMethods(['executeCommand'])
->getMock();
$redis->expects($this->never())
->method('executeCommand');
Yii::$app->set('redis', $redis);
/** @var RateLimiter|\PHPUnit\Framework\MockObject\MockObject $filter */
$filter = $this->getMockBuilder(RateLimiter::class)
->setConstructorArgs([[
'authserverDomain' => 'authserver.ely.by',
]])
->setMethods(['getServer'])
->getMock();
$filter->method('getServer')
->willReturn(new OauthClient());
$filter->checkRateLimit(null, new Request(), null, null);
}
public function testCheckRateLimiterWithValidServerId() {
/** @var Connection|\PHPUnit\Framework\MockObject\MockObject $redis */
$redis = $this->getMockBuilder(Connection::class)
->setMethods(['executeCommand'])
->getMock();
$redis->expects($this->never())
->method('executeCommand');
Yii::$app->set('redis', $redis);
/** @var Request|\PHPUnit\Framework\MockObject\MockObject $request */
$request = $this->getMockBuilder(Request::class)
->setMethods(['getHostInfo'])
->getMock();
$request->method('getHostInfo')
->willReturn('http://authserver.ely.by');
$filter = new RateLimiter([
'authserverDomain' => 'authserver.ely.by',
]);
$filter->checkRateLimit(null, $request, null, null);
}
/**
* @expectedException \yii\web\TooManyRequestsHttpException
*/
public function testCheckRateLimiter() {
/** @var Connection|\PHPUnit\Framework\MockObject\MockObject $redis */
$redis = $this->getMockBuilder(Connection::class)
->setMethods(['executeCommand'])
->getMock();
$redis->expects($this->exactly(5))
->method('executeCommand')
->will($this->onConsecutiveCalls('1', '1', '2', '3', '4'));
Yii::$app->set('redis', $redis);
/** @var Request|\PHPUnit\Framework\MockObject\MockObject $request */
$request = $this->getMockBuilder(Request::class)
->setMethods(['getUserIP'])
->getMock();
$request->method('getUserIp')
->willReturn(Internet::localIpv4());
/** @var RateLimiter|\PHPUnit\Framework\MockObject\MockObject $filter */
$filter = $this->getMockBuilder(RateLimiter::class)
->setConstructorArgs([[
'limit' => 3,
'authserverDomain' => 'authserver.ely.by',
]])
->setMethods(['getServer'])
->getMock();
$filter->method('getServer')
->willReturn(null);
for ($i = 0; $i < 5; $i++) {
$filter->checkRateLimit(null, $request, null, null);
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace api\tests\unit\request;
use api\request\RequestParser;
use api\tests\unit\TestCase;
class RequestParserTest extends TestCase {
public function testParse() {
$parser = new RequestParser();
$_POST = ['from' => 'post'];
$this->assertEquals(['from' => 'post'], $parser->parse('from=post', ''));
$this->assertEquals(['from' => 'post'], $parser->parse('', ''));
$_POST = [];
$this->assertEquals(['from' => 'json'], $parser->parse('{"from":"json"}', ''));
$this->assertEquals(['from' => 'body'], $parser->parse('from=body', ''));
$this->assertEquals(['onlykey' => ''], $parser->parse('onlykey', ''));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace api\tests\_support\traits;
use api\traits\AccountFinder;
use Codeception\Specify;
use common\models\Account;
use api\tests\unit\TestCase;
use common\tests\fixtures\AccountFixture;
class AccountFinderTest extends TestCase {
use Specify;
public function _fixtures() {
return [
'accounts' => AccountFixture::class,
];
}
public function testGetAccount() {
$model = new AccountFinderTestTestClass();
/** @var Account $account */
$accountFixture = $this->tester->grabFixture('accounts', 'admin');
$model->login = $accountFixture->email;
$account = $model->getAccount();
$this->assertInstanceOf(Account::class, $account);
$this->assertSame($accountFixture->id, $account->id, 'founded account for passed login data');
$model = new AccountFinderTestTestClass();
$model->login = 'unexpected';
$this->assertNull($account = $model->getAccount(), 'null, if account can\'t be found');
}
public function testGetLoginAttribute() {
$model = new AccountFinderTestTestClass();
$model->login = 'erickskrauch@ely.by';
$this->assertEquals('email', $model->getLoginAttribute(), 'if login look like email value, then \'email\'');
$model = new AccountFinderTestTestClass();
$model->login = 'erickskrauch';
$this->assertEquals('username', $model->getLoginAttribute(), 'username in any other case');
}
}
class AccountFinderTestTestClass {
use AccountFinder;
public $login;
public function getLogin(): string {
return $this->login;
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace codeception\api\unit\validators;
use api\validators\EmailActivationKeyValidator;
use Codeception\Specify;
use common\helpers\Error as E;
use common\models\confirmations\ForgotPassword;
use common\models\EmailActivation;
use api\tests\unit\TestCase;
use common\tests\_support\ProtectedCaller;
use common\tests\fixtures\EmailActivationFixture;
use yii\base\Model;
class EmailActivationKeyValidatorTest extends TestCase {
use Specify;
use ProtectedCaller;
public function testValidateAttribute() {
/** @var Model $model */
$model = new class extends Model {
public $key;
};
/** @var EmailActivationKeyValidator|\PHPUnit_Framework_MockObject_MockObject $validator */
$validator = $this->getMockBuilder(EmailActivationKeyValidator::class)
->setMethods(['findEmailActivationModel'])
->getMock();
$expiredActivation = new ForgotPassword();
$expiredActivation->created_at = time() - $expiredActivation->expirationTimeout - 10;
$validActivation = new EmailActivation();
$validator->expects($this->exactly(3))
->method('findEmailActivationModel')
->willReturnOnConsecutiveCalls(null, $expiredActivation, $validActivation);
$validator->validateAttribute($model, 'key');
$this->assertEquals([E::KEY_REQUIRED], $model->getErrors('key'));
$this->assertNull($model->key);
$model->clearErrors();
$model->key = 'original value';
$validator->validateAttribute($model, 'key');
$this->assertEquals([E::KEY_NOT_EXISTS], $model->getErrors('key'));
$this->assertEquals('original value', $model->key);
$model->clearErrors();
$validator->validateAttribute($model, 'key');
$this->assertEquals([E::KEY_EXPIRE], $model->getErrors('key'));
$this->assertEquals('original value', $model->key);
$model->clearErrors();
$validator->validateAttribute($model, 'key');
$this->assertEmpty($model->getErrors('key'));
$this->assertEquals($validActivation, $model->key);
}
public function testFindEmailActivationModel() {
$this->tester->haveFixtures(['emailActivations' => EmailActivationFixture::class]);
$key = $this->tester->grabFixture('emailActivations', 'freshRegistrationConfirmation')['key'];
$model = new EmailActivationKeyValidator();
/** @var EmailActivation $result */
$result = $this->callProtected($model, 'findEmailActivationModel', $key);
$this->assertInstanceOf(EmailActivation::class, $result, 'valid key without specifying type must return model');
$this->assertEquals($key, $result->key);
/** @var EmailActivation $result */
$result = $this->callProtected($model, 'findEmailActivationModel', $key, 0);
$this->assertInstanceOf(EmailActivation::class, $result, 'valid key with valid type must return model');
/** @var EmailActivation $result */
$result = $this->callProtected($model, 'findEmailActivationModel', $key, 1);
$this->assertNull($result, 'valid key, but invalid type must return null');
$model = new EmailActivationKeyValidator();
$result = $this->callProtected($model, 'findEmailActivationModel', 'invalid-key');
$this->assertNull($result, 'invalid key must return null');
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace codeception\api\unit\validators;
use api\validators\PasswordRequiredValidator;
use common\helpers\Error as E;
use common\models\Account;
use common\rbac\Permissions as P;
use api\tests\unit\TestCase;
use common\tests\_support\ProtectedCaller;
use yii\web\User;
class PasswordRequiredValidatorTest extends TestCase {
use ProtectedCaller;
public function testValidateValue() {
$account = new Account(['password' => '12345678']);
$model = new PasswordRequiredValidator(['account' => $account]);
// Get error.password_required if password is empty
$this->assertEquals([E::PASSWORD_REQUIRED, []], $this->callProtected($model, 'validateValue', ''));
// Get error.password_incorrect if password is incorrect
$this->assertEquals([E::PASSWORD_INCORRECT, []], $this->callProtected($model, 'validateValue', '87654321'));
// No errors, if password is correct for provided account
$this->assertNull($this->callProtected($model, 'validateValue', '12345678'));
// Skip validation if user can skip identity verification
/** @var User|\Mockery\MockInterface $component */
$component = mock(User::class . '[can]', [['identityClass' => '']]);
$component->shouldReceive('can')->withArgs([P::ESCAPE_IDENTITY_VERIFICATION])->andReturn(true);
$model->user = $component;
$this->assertNull($this->callProtected($model, 'validateValue', ''));
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace api\tests\unit\validators;
use api\validators\TotpValidator;
use common\helpers\Error as E;
use common\models\Account;
use OTPHP\TOTP;
use api\tests\unit\TestCase;
use common\tests\_support\ProtectedCaller;
class TotpValidatorTest extends TestCase {
use ProtectedCaller;
public function testValidateValue() {
$account = new Account();
$account->otp_secret = 'AAAA';
$controlTotp = TOTP::create($account->otp_secret);
$validator = new TotpValidator(['account' => $account]);
$result = $this->callProtected($validator, 'validateValue', 123456);
$this->assertEquals([E::TOTP_INCORRECT, []], $result);
$result = $this->callProtected($validator, 'validateValue', $controlTotp->now());
$this->assertNull($result);
$result = $this->callProtected($validator, 'validateValue', $controlTotp->at(time() - 31));
$this->assertEquals([E::TOTP_INCORRECT, []], $result);
$validator->window = 2;
$result = $this->callProtected($validator, 'validateValue', $controlTotp->at(time() - 31));
$this->assertNull($result);
$at = time() - 400;
$validator->timestamp = $at;
$result = $this->callProtected($validator, 'validateValue', $controlTotp->now());
$this->assertEquals([E::TOTP_INCORRECT, []], $result);
$result = $this->callProtected($validator, 'validateValue', $controlTotp->at($at));
$this->assertNull($result);
$at = function() {
return null;
};
$validator->timestamp = $at;
$result = $this->callProtected($validator, 'validateValue', $controlTotp->now());
$this->assertNull($result);
$at = function() {
return time() - 700;
};
$validator->timestamp = $at;
$result = $this->callProtected($validator, 'validateValue', $controlTotp->at($at()));
$this->assertNull($result);
}
}