Реализована логика oAuth авторизации приложений, добавлен Redis, удалены лишние тесты, пофикшены старые.

This commit is contained in:
ErickSkrauch
2016-02-14 20:50:10 +03:00
parent 59addfac07
commit f5f93ddef1
52 changed files with 1752 additions and 317 deletions

View File

@@ -21,3 +21,6 @@ $_SERVER['SERVER_NAME'] = parse_url(\Codeception\Configuration::config()['confi
$_SERVER['SERVER_PORT'] = parse_url(\Codeception\Configuration::config()['config']['test_entry_url'], PHP_URL_PORT) ?: '80';
Yii::setAlias('@tests', dirname(dirname(__DIR__)));
// disable deep cloning of properties inside specify block
\Codeception\Specify\Config::setDeepClone(false);

View File

@@ -0,0 +1,21 @@
<?php
namespace tests\codeception\api\_pages;
use yii\codeception\BasePage;
/**
* @property \tests\codeception\api\FunctionalTester $actor
*/
class OauthRoute extends BasePage {
public function validate($queryParams) {
$this->route = ['oauth/validate'];
$this->actor->sendGET($this->getUrl($queryParams));
}
public function complete($queryParams = [], $postParams = []) {
$this->route = ['oauth/complete'];
$this->actor->sendPOST($this->getUrl($queryParams), $postParams);
}
}

View File

@@ -13,6 +13,11 @@ modules:
- Yii2
- tests\codeception\common\_support\FixtureHelper
- REST
- Redis
config:
Yii2:
configFile: '../config/api/functional.php'
Redis:
host: localhost
port: 6379
database: 1

View File

@@ -1,47 +0,0 @@
<?php
use tests\codeception\api\FunctionalTester;
use tests\codeception\api\_pages\ContactPage;
/* @var $scenario Codeception\Scenario */
$I = new FunctionalTester($scenario);
$I->wantTo('ensure that contact works');
$contactPage = ContactPage::openBy($I);
$I->see('Contact', 'h1');
$I->amGoingTo('submit contact form with no data');
$contactPage->submit([]);
$I->expectTo('see validations errors');
$I->see('Contact', 'h1');
$I->see('Name cannot be blank', '.help-block');
$I->see('Email cannot be blank', '.help-block');
$I->see('Subject cannot be blank', '.help-block');
$I->see('Body cannot be blank', '.help-block');
$I->see('The verification code is incorrect', '.help-block');
$I->amGoingTo('submit contact form with not correct email');
$contactPage->submit([
'name' => 'tester',
'email' => 'tester.email',
'subject' => 'test subject',
'body' => 'test content',
'verifyCode' => 'testme',
]);
$I->expectTo('see that email adress is wrong');
$I->dontSee('Name cannot be blank', '.help-block');
$I->see('Email is not a valid email address.', '.help-block');
$I->dontSee('Subject cannot be blank', '.help-block');
$I->dontSee('Body cannot be blank', '.help-block');
$I->dontSee('The verification code is incorrect', '.help-block');
$I->amGoingTo('submit contact form with correct data');
$contactPage->submit([
'name' => 'tester',
'email' => 'tester@example.com',
'subject' => 'test subject',
'body' => 'test content',
'verifyCode' => 'testme',
]);
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');

View File

@@ -0,0 +1,294 @@
<?php
namespace tests\codeception\api;
use tests\codeception\api\_pages\OauthRoute;
use tests\codeception\api\functional\_steps\AccountSteps;
class OauthCest {
/**
* @var OauthRoute
*/
private $route;
public function _before(FunctionalTester $I) {
$this->route = new OauthRoute($I);
}
public function testValidateRequest(FunctionalTester $I) {
$this->testOauthParamsValidation($I, 'validate');
$I->wantTo('validate and obtain information about new auth request');
$this->route->validate($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
[
'minecraft_server_session'
],
'test-state'
));
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'success' => true,
'oAuth' => [
'client_id' => 'ely',
'redirect_uri' => 'http://ely.by',
'response_type' => 'code',
'scope' => 'minecraft_server_session',
'state' => 'test-state',
],
'client' => [
'id' => 'ely',
'name' => 'Ely.by',
'description' => 'Всем знакомое елуби',
],
'session' => [
'scopes' => [
'minecraft_server_session',
],
],
]);
}
public function testValidateWithDescriptionReplaceRequest(FunctionalTester $I) {
$I->wantTo('validate and get information with description replacement');
$this->route->validate($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
null,
null,
[
'description' => 'all familiar eliby',
]
));
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'client' => [
'description' => 'all familiar eliby',
],
]);
}
public function testCompleteValidationAction($I, $scenario) {
$I = new AccountSteps($scenario);
$I->loggedInAsActiveAccount();
$I->wantTo('validate all oAuth params on complete request');
$this->testOauthParamsValidation($I, 'complete');
}
public function testCompleteActionOnWrongConditions($I, $scenario) {
$I = new AccountSteps($scenario);
$I->loggedInAsActiveAccount();
$I->wantTo('get accept_required if I dom\'t require any scope, but this is first time request');
$I->cleanupRedis();
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code'
));
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'accept_required',
'parameter' => '',
'statusCode' => 401,
]);
$I->wantTo('get accept_required if I require some scopes on first time');
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
['minecraft_server_session']
));
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'accept_required',
'parameter' => '',
'statusCode' => 401,
]);
}
public function testCompleteActionSuccess($I, $scenario) {
$I = new AccountSteps($scenario);
$I->loggedInAsActiveAccount();
$I->wantTo('get auth code if I require some scope and pass accept field');
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
['minecraft_server_session']
), ['accept' => true]);
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseContainsJson([
'success' => true,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
$I->wantTo('get auth code if I don\'t require any scope and don\'t pass accept field, but previously have ' .
'successful request');
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code'
));
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseContainsJson([
'success' => true,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
$I->wantTo('get auth code if I require some scopes and don\'t pass accept field, but previously have successful ' .
'request with same scopes');
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
['minecraft_server_session']
));
$I->canSeeResponseCodeIs(200);
$I->canSeeResponseContainsJson([
'success' => true,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
}
public function testAcceptRequiredOnNewScope($I, $scenario) {
$I = new AccountSteps($scenario);
$I->loggedInAsActiveAccount();
$I->wantTo('get accept_required if I have previous successful request, but now require some new scope');
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
['minecraft_server_session']
), ['accept' => true]);
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
['minecraft_server_session', 'change_skin']
));
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'accept_required',
'parameter' => '',
'statusCode' => 401,
]);
}
public function testCompleteActionWithDismissState($I, $scenario) {
$I = new AccountSteps($scenario);
$I->loggedInAsActiveAccount();
$I->wantTo('get access_denied error if I pass accept in false state');
$this->route->complete($this->buildQueryParams(
'ely',
'http://ely.by',
'code',
['minecraft_server_session']
), ['accept' => false]);
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'access_denied',
'parameter' => '',
'statusCode' => 401,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
}
private function buildQueryParams(
$clientId = null,
$redirectUri = null,
$responseType = null,
$scopes = [],
$state = null,
$customData = []
) {
$params = $customData;
if ($clientId !== null) {
$params['client_id'] = $clientId;
}
if ($redirectUri !== null) {
$params['redirect_uri'] = $redirectUri;
}
if ($responseType !== null) {
$params['response_type'] = $responseType;
}
if ($state !== null) {
$params['state'] = $state;
}
if (!empty($scopes)) {
if (is_array($scopes)) {
$scopes = implode(',', $scopes);
}
$params['scope'] = $scopes;
}
return $params;
}
private function testOauthParamsValidation(FunctionalTester $I, $action) {
$I->wantTo('check behavior on invalid request without one or few params');
$this->route->$action($this->buildQueryParams());
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'invalid_request',
'parameter' => 'client_id',
'statusCode' => 400,
]);
$I->wantTo('check behavior on invalid client id');
$this->route->$action($this->buildQueryParams('non-exists-client', 'http://some-resource.by', 'code'));
$I->canSeeResponseCodeIs(401);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'invalid_client',
'statusCode' => 401,
]);
$I->wantTo('check behavior on invalid response type');
$this->route->$action($this->buildQueryParams('ely', 'http://ely.by', 'kitty'));
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'unsupported_response_type',
'parameter' => 'kitty',
'statusCode' => 400,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
$I->wantTo('check behavior on some invalid scopes');
$this->route->$action($this->buildQueryParams('ely', 'http://ely.by', 'code', [
'minecraft_server_session',
'some_wrong_scope',
]));
$I->canSeeResponseCodeIs(400);
$I->canSeeResponseIsJson();
$I->canSeeResponseContainsJson([
'success' => false,
'error' => 'invalid_scope',
'parameter' => 'some_wrong_scope',
'statusCode' => 400,
]);
$I->canSeeResponseJsonMatchesJsonPath('$.redirectUri');
}
}

View File

@@ -1,3 +1,5 @@
<?php
new yii\web\Application(require(dirname(dirname(__DIR__)) . '/config/api/functional.php'));
\Codeception\Util\Autoload::registerSuffix('Steps', __DIR__ . DIRECTORY_SEPARATOR);

View File

@@ -0,0 +1,16 @@
<?php
namespace tests\codeception\api\functional\_steps;
use tests\codeception\api\_pages\LoginRoute;
use tests\codeception\api\FunctionalTester;
class AccountSteps extends FunctionalTester {
public function loggedInAsActiveAccount() {
$I = $this;
$route = new LoginRoute($I);
$route->login('Admin', 'password_0');
$I->canSeeResponseIsJson();
}
}

View File

@@ -1,59 +0,0 @@
<?php
namespace tests\codeception\api\unit\models;
use Yii;
use tests\codeception\api\unit\TestCase;
use api\models\ContactForm;
class ContactFormTest extends TestCase
{
use \Codeception\Specify;
protected function setUp()
{
parent::setUp();
Yii::$app->mailer->fileTransportCallback = function ($mailer, $message) {
return 'testing_message.eml';
};
}
protected function tearDown()
{
unlink($this->getMessageFile());
parent::tearDown();
}
public function testContact()
{
$model = new ContactForm();
$model->attributes = [
'name' => 'Tester',
'email' => 'tester@example.com',
'subject' => 'very important letter subject',
'body' => 'body of current message',
];
$model->sendEmail('admin@example.com');
$this->specify('email should be send', function () {
expect('email file should exist', file_exists($this->getMessageFile()))->true();
});
$this->specify('message should contain correct data', function () use ($model) {
$emailMessage = file_get_contents($this->getMessageFile());
expect('email should contain user name', $emailMessage)->contains($model->name);
expect('email should contain sender email', $emailMessage)->contains($model->email);
expect('email should contain subject', $emailMessage)->contains($model->subject);
expect('email should contain body', $emailMessage)->contains($model->body);
});
}
private function getMessageFile()
{
return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml';
}
}

View File

@@ -1,87 +0,0 @@
<?php
namespace tests\codeception\api\models;
use Yii;
use tests\codeception\api\unit\DbTestCase;
use api\models\PasswordResetRequestForm;
use tests\codeception\common\fixtures\UserFixture;
use common\models\Account;
use Codeception\Specify;
class PasswordResetRequestFormTest extends DbTestCase
{
use Specify;
protected function setUp()
{
parent::setUp();
Yii::$app->mailer->fileTransportCallback = function ($mailer, $message) {
return 'testing_message.eml';
};
}
protected function tearDown()
{
@unlink($this->getMessageFile());
parent::tearDown();
}
public function testSendEmailWrongUser()
{
$this->specify('no user with such email, message should not be sent', function () {
$model = new PasswordResetRequestForm();
$model->email = 'not-existing-email@example.com';
expect('email not sent', $model->sendEmail())->false();
});
$this->specify('user is not active, message should not be sent', function () {
$model = new PasswordResetRequestForm();
$model->email = $this->user[1]['email'];
expect('email not sent', $model->sendEmail())->false();
});
}
public function testSendEmailCorrectUser()
{
$model = new PasswordResetRequestForm();
$model->email = $this->user[0]['email'];
$user = Account::findOne(['password_reset_token' => $this->user[0]['password_reset_token']]);
expect('email sent', $model->sendEmail())->true();
expect('user has valid token', $user->password_reset_token)->notNull();
$this->specify('message has correct format', function () use ($model) {
expect('message file exists', file_exists($this->getMessageFile()))->true();
$message = file_get_contents($this->getMessageFile());
expect('message "from" is correct', $message)->contains(Yii::$app->params['supportEmail']);
expect('message "to" is correct', $message)->contains($model->email);
});
}
public function fixtures()
{
return [
'user' => [
'class' => UserFixture::className(),
'dataFile' => '@tests/codeception/api/unit/fixtures/data/models/user.php'
],
];
}
private function getMessageFile()
{
return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml';
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace tests\codeception\api\unit\models;
use tests\codeception\api\unit\DbTestCase;
use tests\codeception\common\fixtures\UserFixture;
use api\models\ResetPasswordForm;
class ResetPasswordFormTest extends DbTestCase
{
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testResetWrongToken()
{
new ResetPasswordForm('notexistingtoken_1391882543');
}
/**
* @expectedException \yii\base\InvalidParamException
*/
public function testResetEmptyToken()
{
new ResetPasswordForm('');
}
public function testResetCorrectToken()
{
$form = new ResetPasswordForm($this->user[0]['password_reset_token']);
expect('password should be resetted', $form->resetPassword())->true();
}
public function fixtures()
{
return [
'user' => [
'class' => UserFixture::className(),
'dataFile' => '@tests/codeception/api/unit/fixtures/data/models/user.php'
],
];
}
}

View File

@@ -4,6 +4,9 @@ namespace tests\codeception\common\_support;
use Codeception\Module;
use tests\codeception\common\fixtures\AccountFixture;
use tests\codeception\common\fixtures\EmailActivationFixture;
use tests\codeception\common\fixtures\OauthClientFixture;
use tests\codeception\common\fixtures\OauthScopeFixture;
use tests\codeception\common\fixtures\OauthSessionFixture;
use yii\test\FixtureTrait;
use yii\test\InitDbFixture;
@@ -26,35 +29,20 @@ class FixtureHelper extends Module {
getFixture as protected;
}
/**
* Method called before any suite tests run. Loads User fixture login user
* to use in functional tests.
*
* @param array $settings
*/
public function _beforeSuite($settings = []) {
$this->loadFixtures();
}
/**
* Method is called after all suite tests run
*/
public function _afterSuite() {
$this->unloadFixtures();
}
/**
* @inheritdoc
*/
public function globalFixtures() {
return [
InitDbFixture::className(),
];
}
/**
* @inheritdoc
*/
public function fixtures() {
return [
'accounts' => [
@@ -65,6 +53,19 @@ class FixtureHelper extends Module {
'class' => EmailActivationFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/email-activations.php',
],
'oauthClients' => [
'class' => OauthClientFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/oauth-clients.php',
],
'oauthScopes' => [
'class' => OauthScopeFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/oauth-scopes.php',
],
'oauthSessions' => [
'class' => OauthSessionFixture::class,
'dataFile' => '@tests/codeception/common/fixtures/data/oauth-sessions.php',
],
];
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace tests\codeception\common\fixtures;
use common\models\OauthClient;
use yii\test\ActiveFixture;
class OauthClientFixture extends ActiveFixture {
public $modelClass = OauthClient::class;
public $depends = [
AccountFixture::class,
];
}

View File

@@ -0,0 +1,11 @@
<?php
namespace tests\codeception\common\fixtures;
use common\models\OauthScope;
use yii\test\ActiveFixture;
class OauthScopeFixture extends ActiveFixture {
public $modelClass = OauthScope::class;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace tests\codeception\common\fixtures;
use common\models\OauthScope;
use common\models\OauthSession;
use yii\test\ActiveFixture;
class OauthSessionFixture extends ActiveFixture {
public $modelClass = OauthSession::class;
public $depends = [
OauthClientFixture::class,
AccountFixture::class,
];
}

View File

@@ -0,0 +1,23 @@
<?php
return [
'ely' => [
'id' => 'ely',
'secret' => 'ZuM1vGchJz-9_UZ5HC3H3Z9Hg5PzdbkM',
'name' => 'Ely.by',
'description' => 'Всем знакомое елуби',
'redirect_uri' => 'http://ely.by',
'account_id' => NULL,
'is_trusted' => 0,
'created_at' => 1455309271,
],
'tlauncher' => [
'id' => 'tlauncher',
'secret' => 'HsX-xXzdGiz3mcsqeEvrKHF47sqiaX94',
'name' => 'TLauncher',
'description' => 'Лучший альтернативный лаунчер для Minecraft с большим количеством версий и их модификаций, а также возмоностью входа как с лицензионным аккаунтом, так и без него.',
'redirect_uri' => '',
'account_id' => NULL,
'is_trusted' => 0,
'created_at' => 1455318468,
],
];

View File

@@ -0,0 +1,9 @@
<?php
return [
'minecraft_server_session' => [
'id' => 'minecraft_server_session',
],
'change_skin' => [
'id' => 'change_skin',
],
];

View File

@@ -0,0 +1,3 @@
<?php
return [
];

View File

@@ -22,5 +22,8 @@ return [
'urlManager' => [
'showScriptName' => true,
],
'redis' => [
'database' => 1,
],
],
];