I am using Codeception and fixtures to test my API but it seems the fixtures are only available for the first test. Here is my tests:
class ActionCest extends BaseTestCase
{
public function _fixtures()
{
return [
'profiles' => [
'class' => UserFixture::className(),
// fixture data located in tests/_data/user.php
'dataFile' => codecept_data_dir() . 'user.php'
],
'actions' => [
'class' => ActionFixture::className(),
'dataFile' => codecept_data_dir() . 'action.php'
],
];
}
public function createAction(ApiTester $I)
{
$user = $I->grabFixture('profiles', 'user1');
$I->wantTo('Add action');
$I->haveHttpHeader('Authorization', 'Bearer '. $user['auth_key']);
$payload = [
'action_id' => 123,
'saved' => true,
'viewed' => false,
'complete' => false,
];
$I->sendPOST('/action/save', $payload);
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
$I->seeResponseContainsJson($this->buildResponsePayload($payload));
}
public function getAction(ApiTester $I)
{
$user = $I->grabFixture('profiles', 'user1');
//$action = $I->grabFixture('actions', 'action1');
$I->wantTo('Retrieve action');
$I->haveHttpHeader('Authorization', 'Bearer '. $user['auth_key']);
$I->sendPOST('/action/get-by-id/1');//'. $action['action_id']);
$I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
$I->seeResponseContainsJson($this->buildResponsePayload($payload));
}
}
In the example above, the first test will pass OK. However the second test will fail due the user not being authenticated. I assume that the user has been removed from the database after the first test.
How would I overcome this?
The answer was to replace the _fixtures() method with the following method:
public function _before(ApiTester $I)
{
$I->haveFixtures([
'users' => [
'class' => UserFixture::className(),
'dataFile' => codecept_data_dir() . 'user.php',
],
'actions' => [
'class' => UserActionFixture::className(),
'dataFile' => codecept_data_dir() . 'action.php',
],
]);
}
Related
So I've got an issue with element-api in php, which is a plugin for CRAFT cms, could someone help - what am I doing wrong here? I can't print time.json - always getting :
{"error":{"code":0,"message":"Class name must be a valid object or a
string"}}
here's the code:
<?php
use Craft;
use craft\elements\Entry;
use craft\controllers\TemplatesController;
use League\Fractal\TransformerAbstract;
use craft\web\View;
use craft\elements\User;
return [
'endpoints' => [
'tooltip-modal.json' => function() {
$affiliateId = Craft::$app->request->getParam('entryId');
$siteLanguage = Craft::$app->request->getParam('siteLanguage');
$data = [
'elementType' => Entry::class,
'paginate' => false,
'transformer' => function(Entry $affiliate) {
$view = Craft::$app->getView();
// $view->setTemplateMode($view::TEMPLATE_MODE_SITE);
return [
'affiliate' => $view->renderTemplate('modals/tooltipAffModal.twig', ['affiliate' => $affiliate])
];
},
'criteria' => ['id' => $affiliateId, 'site' => $siteLanguage]
];
return $data;
},
'time.json' => function() {
// $userId = Craft::$app->request->request->getParam('userId');
$data = [
'elementType' => User::class,
'paginate' => false,
'transformer' => function(User $user) {
return [
'date' => $user->winHistory::find()->orderBy(['id' => SORT_DESC])->one()
];
},
// 'criteria' => ['id' => $affiliateId, 'site' => $siteLanguage]
];
return $data;
}
]
];
I have a laravel 5.7 application where I want to add prooph for event sourcing. I have follow the instructions but I retrieve this error:
Prooph\ServiceBus\Exception\RuntimeException: CommandBus was not able to identify a CommandHandler for command App\src\Prooph\Model\Procedure\Command\ChangeProcedureState in /var/www/html/vendor/prooph/service-bus/src/CommandBus.php:58
This is my config/prooph.php file
return [
'event_store' => [
'adapter' => [
'type' => \Prooph\EventStore\Pdo\MySqlEventStore::class,
'options' => [
'connection_alias' => 'laravel.connections.pdo',
],
],
'plugins' => [
\Prooph\EventStoreBusBridge\EventPublisher::class,
\Prooph\EventStoreBusBridge\TransactionManager::class,
],
'procedure_collection' => [
'repository_class' => App\src\Prooph\Infrastructure\Repository\EventStoreProcedureCollection::class,
'aggregate_type' => App\src\Prooph\Model\Procedure\Procedure::class,
],
],
'service_bus' => [
'command_bus' => [
'router' => [
'type' => \Prooph\ServiceBus\Plugin\Router\CommandRouter::class,
'routes' => [
// list of commands with corresponding command handler
\App\src\Prooph\Model\Procedure\Command\ChangeProcedureState::class => App\src\Prooph\Model\Procedure\Handler\ChangeProcedureStateHandler::class,
],
],
],
'event_bus' => [
'plugins' => [
\Prooph\ServiceBus\Plugin\InvokeStrategy\OnEventStrategy::class,
],
'router' => [
'routes' => [
// list of events with a list of projectors
],
],
],
/*'event_bus' => [
'router' => [
'routes' => [
// list of events with a list of projectors
App\src\Prooph\ProophessorDo\Model\User\Event\UserWasRegistered::class => [
\App\src\Prooph\ProophessorDo\Projection\User\UserProjector::class
],
],
],
],*/
],
];
This is my service that dispatch the command
class ProcedureRetrieveStateChanged
{
/** #var \FluentProceduresRepository $procedureRepository */
private $procedureRepository;
/**
* #var CommandBus
*/
private $commandBus;
public function __construct(\FluentProceduresRepository $procedureRepository, CommandBus $commandBus)
{
$this->procedureRepository = $procedureRepository;
$this->commandBus = $commandBus;
}
public function execute($procedureId, array $groups)
{
$procedure = $this->procedureRepository->find($procedureId);
if (null === $procedure) {
return false;
}
foreach ($groups as $group) {
$actualState = $procedure->getStatebyGroup($group['pivot']['group_id']);
if ($actualState !== $group['pivot']['state']) {
$command = new ChangeProcedureState(
[
'procedure_id' => $procedureId,
'group_id' => $group['pivot']['group_id'],
'old_state' => $actualState,
'new_state' => $group['pivot']['state'],
]
);
$this->commandBus->dispatch($command);
}
}
}
}
This is my command
final class ChangeProcedureState extends Command implements PayloadConstructable
{
use PayloadTrait;
public function procedureId(): ProcedureId
{
return ProcedureId::fromString($this->payload['procedure_id']);
}
public function state(): string
{
return $this->payload['state'];
}
protected function setPayload(array $payload): void
{
Assertion::keyExists($payload, 'procedure_id');
Assertion::keyExists($payload, 'group_id');
Assertion::keyExists($payload, 'old_state');
Assertion::keyExists($payload, 'new_state');
$this->payload = $payload;
}
}
And this is my handler
class ChangeProcedureStateHandler
{
/**
* #var ProcedureCollection
*/
private $procedureCollection;
public function __construct(ProcedureCollection $procedureCollection)
{
$this->procedureCollection = $procedureCollection;
}
public function __invoke(ChangeProcedureState $command): void
{
$procedure = $this->procedureCollection->get($command->procedureId());
$procedure->changeState($command->state());
$this->procedureCollection->save($procedure);
}
}
Can someone help me with this problem?
To use handlers as a string you need to use ServiceLocatorPlugin, but before that you need to register all handlers in laravel container, like $app->singletor...or $app->bind.
Cheers.
I've a problem to use the zf-hal hydrator in combination with an abstract factory. This is my module configuration:
public function getConfig()
{
return [
'hydrators' => [
'abstract_factories' => [
AbstractHydratorFactory::class,
]
],
'service_manager' => [
'factories' => [
ModuleOptions::class => ModuleOptionsFactory::class,
],
],
'zf-hal' => [
'renderer' => [
'default_hydrator' => 'reflection'
],
]
];
}
My abstract factory looks like this:
class AbstractHydratorFactory implements AbstractFactoryInterface
{
public function canCreate(ContainerInterface $container, $requestedName)
{
$moduleOptions = $container->get(ModuleOptions::class);
$configuration = $moduleOptions->getClass();
return isset($configuration[$requestedName]['property_name_translation']);
}
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$moduleOptions = $container->get(ModuleOptions::class);
$configuration = $moduleOptions->getClass();
$hydrator = $container->get($moduleOptions->getHydrator());
$hydrator->setNamingStrategy(
new ArrayMapNamingStrategy(
$configuration[$requestedName]['property_name_translation']
)
);
return $hydrator;
}
}
To test my module I created some unit tests. One of them is:
class HalEntityHydratorTest extends TestCase
{
protected $moduleLoader;
protected function setUp()
{
$this->moduleLoader = new ModuleLoader([
'modules' => [
'Zend\Hydrator',
'Zend\Router',
'ZF\Hal',
'MyHalHydratorModule',
'MyHalHydratorModuleTest\Integration\Hydrator\HalEntityHydratorTest',
],
'module_listener_options' => [],
]);
$this->moduleLoader->getApplication()->bootstrap();
}
public function testHalRendererWithHalEntities()
{
$halPlugin = $this->moduleLoader->getServiceManager()->get('ViewHelperManager')->get('Hal');
$rootTestEntity = new RootTestEntity();
$childTestEntity = new ChildTestEntity();
$rootTestEntity->setChildEntity(new Entity($childTestEntity));
$rootTestEntity->setUnkownChildEntity(new Entity(new UnkownChildTestEntity()));
$expectedArray = [
'_embedded' => [
'phpunit:test-entity' => [
'_links' => [],
],
'unkownChildEntity' => [
'unkownChildTestProperty' => 'phpunit',
'_links' => [],
],
],
'_links' => [],
];
$this->assertSame($expectedArray, $halPlugin->renderEntity(new Entity($rootTestEntity)));
}
}
These are my test entities:
class ChildTestEntity
{
}
class UnkownChildTestEntity
{
protected $unkownChildTestProperty = 'phpunit';
}
class RootTestEntity
{
protected $childEntity;
protected $unkownChildEntity;
public function setUnkownChildEntity($unkownChildEntity)
{
$this->unkownChildEntity = $unkownChildEntity;
}
public function setChildEntity($childEntity)
{
$this->childEntity = $childEntity;
}
}
And then it could be good to know what my test module configuration looks like:
public function getConfig()
{
return [
'zf-hal' => [
'metadata_map' => [
ChildTestEntity::class => [
'force_self_link' => false,
],
UnkownChildTestEntity::class => [
'force_self_link' => false,
],
],
],
'my-hal-hydrator-module' => [
'class' => [
RootTestEntity::class => [
'property_name_translation' => [
'childEntity' => 'phpunit:test-entity',
],
],
],
],
];
}
Ok, enough sourcecode. What happens now?
I run my test suite and the test above fails because of the arrays are different. That's why the first key of the result array is still 'childEntity' and not 'phpunit:test-entity' as expected.
So I think the property replacement has not take place but I don't know why.
I am using google authclient login in my application.Google login is working perfectly but I am unable to get the user attributes.
web.php
'google' => [
'class' => 'yii\authclient\clients\GoogleOAuth',
'clientId' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'clientSecret' => 'xxxxxxxxxxxxxxxxxxxxxxxx',
'scope'=>' https://www.googleapis.com/auth/plus.profile.emails.read',
'returnUrl'=>'https://localhost/mysite',
],
sitecontroller.php
public function actions()
{
return [
'auth' => [
'class' =>'yii\authclient\AuthAction',
'successCallback' => [
$this, 'successCallback'
],
],
];
}
successcallback()
public function successCallback($client)
{
$attributes = $client->getUserAttributes();
// print_r($attributes);die();
// user login or signup comes here
// print_r($attributes['email']);die();
$user_email = $attributes['email'];
$user_name = $attributes['name'];
// echo $user_email;echo '<br>';
// echo $user_name;
// die();
$user = User::find()->where(['user_email'=>$user_email])->one();
$count = count($user);
if ($count == 0 ){
return $this->redirect(['user/create?email='.$user_email.'&name='.$user_name.'']);
}else{
return Yii::$app->user->login($user);
//echo Yii::$app->user->identity->id;die();
}
Could any one help me to achieve this..
Thank you..
set your returnUrl in web.php this : https://test.com/site/auth?authclient=google
your code must be like below :
'google' => [
'class' => 'yii\authclient\clients\Google',
'clientId' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'clientSecret' => 'xxxxxxxxxxxxxxxxxxxxxxxx',
'returnUrl'=>'https://test.com/site/auth?authclient=google',
],
I am using Access Control Filter for access managing, but can't get one thing done - for example, how can I allow just project manager to update project and forbid it to others? I tried it via matchCallback, but in this case all project managers can update any project because TRUE is returned.
Similar more often required rules - how to allow user to update/delete posts where he is author using ACF?
'access' => [
'class' => AccessControl::className(),
'only' => ['index', 'view', 'create', 'update', 'delete'],
'rules' => [
[
'actions' => ['update'],
'allow' => true,
'roles' => ['#'],
'matchCallback' => function ($rule, $action) {
return Yii::$app->user->identity->getProjectParticipants()
->one()->isManager(Yii::$app->user->identity->id);
}
],
],
],
It could be implemented something like this:
use Yii;
use yii\web\Controller;
use yii\filters\AccessControl;
class MyController extends Controller
{
...
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['update', 'delete'],
'rules' => [
[
'actions' => ['update', 'delete'],
'allow' => true,
'roles' => ['#'],
'matchCallback' => function ($rule, $action) {
if (Yii::$app->user->can('admin') || $this->isUserAuthor()) {
return true;
}
return false;
}
],
],
],
];
}
protected function findModel($id)
{
if (($model = MyModel::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
protected function isUserAuthor()
{
return $this->findModel(Yii::$app->request->get('id'))->author->id == Yii::$app->user->id;
}
...
}
This is bested solved with a custom AccessRule. One would have to fill in the code to check if a user is the author of a project.
namespace app\filters;
class AuthorAccessRule extends \yii\filters\AccessRule
{
public $allow = true; // Allow access if this rule matches
public $roles = ['#']; // Ensure user is logged in.
public function allows($action, $user, $request)
{
$parentRes = parent::allows($action, $user, $request);
// $parentRes can be `null`, `false` or `true`.
// True means the parent rule matched and allows access.
if ($parentRes !== true) {
return $parentRes;
}
return ($this->getProjectAuthorId($request) == $user->id);
}
private function getProjectAuthorId($request)
{
// Fill in code to receive the right project.
// assuming the project id is given à la `project/update?id=1`
$projectId = $request->get('id');
$project = \app\models\Project::findOne($projectId);
return isset($project) ? $project->author_id : null;
}
}
The rule can be used by including this in the behaviors:
'authorAccess' => [
'class' => AccessControl::className(),
'only' => ['update'],
'rules' => ['actions' => ['update']],
'ruleConfig' => ['class' => '\app\filters\AuthorAccessRule'],
],
Following is how I do it with combination of ACF and RBAC. Please correct me if I am wrong or there is better way of doing it. It's based on Basic template.
User's role is stored in the "role" column of the "user" table. Another table "country" is used in this example. Assume you have generated models and controllers using Gii.
Customise PhpManager to use role from database table "user".
class PhpManager extends \yii\rbac\PhpManager
{
public function init()
{
parent::init();
}
public function getAssignments($userId)
{
if (!Yii::$app->user->isGuest) {
$assignment = new Assignment();
$assignment->userId = $userId;
# Assume the role is stored in User table "role" column
$assignment->roleName = Yii::$app->user->identity->role;
return [$assignment->roleName => $assignment];
}
}
}
3. Add authManager to web.app and console.app console file.
'authManager' => [
'class' => 'app\components\PhpManager',
'defaultRoles' => ['user', 'manager', 'admin', 'master'],
],
Create a customized AccessRule. Reference from speixoto's blog.
# Reference
# http://programming.peixoto.cf/2015/01/14/yii2-role-based-access-control-and-context-access-rule/#$$nmvkr0&&0SUmhOPVEeSW9grIhAgzZg$$
class ContextAccessRule extends AccessRule
{
public $modelClass;
public $primaryKey;
protected function matchRole($user)
{
if (parent::matchRole($user))
return true;
$model = $this->findModel();
foreach ($this->roles as $role) {
# Call the CheckAccess() function which process rules
if ($user->can($role, ['model' => $model])) {
return true;
}
}
return false;
}
protected function findModel()
{
if (!isset($this->modelClass))
throw new InvalidConfigException(Yii::t('app', 'the "modelClass" must be set for "{class}".', ['class' => __CLASS__]));
$primaryKey = $this->getPrimaryKey();
# Get the request params
$queryParams = \Yii::$app->getRequest()->getQueryParams();
# Load the model
$model = call_user_func([$this->modelClass, 'findOne'], $queryParams[join(',', $primaryKey)]);
if ($model !== null) {
return $model;
} else {
throw new NotFoundHttpException(Yii::t('app', 'The requested page does not exists.'));
}
}
# Get the primary key of the model
protected function getPrimaryKey()
{
if (!isset($this->primaryKey)) {
return call_user_func([$this->modelClass, 'primaryKey']);
} else {
return $this->primaryKey;
}
}
Create a RbacController.php to generate RBAC files (assignments.php, items.php, rules.php) into rbac folder.
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
$auth->removeAll();
### CREATE & ADD ROLES
$user = $auth->createRole('user');
$node = $auth->createRole('node');
$manager = $auth->createRole('manager');
$admin = $auth->createRole('admin');
$master = $auth->createRole('master');
$auth->add($user);
$auth->add($node);
$auth->add($manager);
$auth->add($admin);
$auth->add($master);
$auth->addChild($manager, $user);
$auth->addChild($manager, $node);
$auth->addChild($admin, $manager);
$auth->addChild($master, $admin);
### ADD RULES
$ownerRule = new \app\components\OwnerRule();
$auth->add($ownerRule);
### CREATE PERMISSIONS ###
$pUpdateOwn = $auth->createPermission('updateOwn');
$pUpdateOwn->description = 'update own';
$pUpdateOwn->ruleName = $ownerRule->name;
$auth->add($pUpdateOwn);
$auth->addChild($pUpdateOwn, $pUpdate);
$pDeleteOwn = $auth->createPermission('deleteOwn');
$pDeleteOwn->description = 'delete own';
$pDeleteOwn->ruleName = $ownerRule->name;
$auth->add($pDeleteOwn);
$auth->addChild($pDeleteOwn, $pDelete);
### ASSIGN PERMISSION TO ROLES
$auth->addChild($user, $pUpdateOwn);
$auth->addChild($user, $pDeleteOwn);
$auth->addChild($manager, $pUpdateOwn);
$auth->addChild($manager, $pDeleteOwn);
}
}
From console, navigate to your project root. Run ./yii rbac/init (for mac) to generate the 3 files into rbac folder.
In CountryController.php, override following function to add "access" behaviors.
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['verbs'] = [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
];
['access'] = [
'class' => AccessControl::className(),
// 'only' => ['view', 'index', 'create', 'update', 'delete'],
'rules' => [
[
'actions' => ['view', 'index'],
'allow' => true,
'roles' => ['?', '#'],
],
[
'actions' => ['create'],
'allow' => true,
// Allow users, manager and admins to create
'roles' => ['user'],
],
[
'class' => 'app\components\ContextAccessRule',
'modelClass' => 'app\models\Country',
'actions' => ['update'],
'allow' => true,
# allow owner and manager to udpate
'roles' => ['updateOwn', 'manager']
],
[
'class' => 'app\components\ContextAccessRule',
'modelClass' => 'app\models\Country',
'actions' => ['delete'],
'allow' => true,
# allow owner and manager to delete
'roles' => ['deleteOwn', 'manager'],
],
],
# if user not login, and not allowed for current action, return following exception
'denyCallback' => function ($rule, $action) {
throw new UnauthorizedHttpException('You are not authorized.');
},
];
return $behaviors;
}
8. Test it out.