How I can mock `Dingo\Api\Auth\Provider\JWT` so I can bypass the Authentication overhwad whilst I am unit testing my endpoints? - phpunit

I am using dingo/api in my api and I want to unit test the endpoint:
class MyApiTest extends TestCase
{
public function testEndpoint()
{
$dispatcher = app('Dingo\Api\Dispatcher');
$fake_token = 'cndksjonsdcnsod';
$dispatcher->header('Authorization', 'Bearer: '.$fake_token);
$dispatcher->version($version)->get('/my-endpoint');
}
}
In my app.php I have the following configuration:
'auth' => [
'jwt' => Dingo\Api\Auth\Provider\JWT::class,
],
Is there a way to mock/fake/set default values to the Dingo\Api\Auth\Provider\JWT provider of jwt authentication?

An approach that worked for me, is via testing the controller itself and mock JWT authentication service by bypassing the Dingo and any middleware used by routing itself.
Example:
Let us suppose we have the following controller:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Tymon\JWTAuth\Facades\JWTAuth;
class ProfileController extends Controller
{
public function getProfile(Request $request,$profile_id)
{
$user = JWTAuth::parseToken()->authenticate();
$language = App::getLocale();
// Do stuff Here
}
}
You can write a simple test:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Tymon\JWTAuth\Facades\JWTAuth;
// Set a test class for speed is ommited
public function testMyApiCall()
{
/**
* $user is an instance of a User
*/
JWTAuth::shouldReceive('parseToken->authenticate')->andReturn($user);
App::setlocale('el');
$request = new Request();
$request->initialize([],['token' => 'AAABBBCCC' ],[],[],[],[],[]);
$controller = new ProfileController();
// I ommit the profile_id value it is just a demonstration
$response = $controller->getProfile($request,$profile_id)
$response_dody = $response->getData(false);
// Perform assertions upon $response_dody
}
In our case we do not care about what routing is used and how it is set up. Therefore, are no mentioning any routing and anything regarding Dingo in this example, we just forget it.
Cons and pros
Though it is not a silver bullet, it is an approach that will give a reliable result focusing on the actual code. Keep in mind though that you bypass many middlewares that may you also want to test as well eg. Authentication ones.
On the other hand you are able to test the logic inside the controller, in cases where the logic is rather small to create a seperate class/method for it eg. selecting data from DB.

Related

excluding tables with setExcludedDoctrineTables when loading fixtures with liip TestFixturesBundle

I'm trying to use Liip test bundle to load fixtures from code using the loadFixtures() method from FixturesTrait
However, I need to exclude the RESOURCE table that I don't want to be dropped in the process. If I understand correctly, this should be easy using the setExcludedDoctrineTables method according to the doc https://github.com/liip/LiipTestFixturesBundle/blob/master/doc/database.md
Unfortunately, when I run this code, the table RESOURCES gets dropped with all the others.
Can anybody see why ?
not sure if that's relevant but I'm using a mysql db in a separate docker container.
<?php
namespace App\Tests\Controller;
use Symfony\Component\Panther\PantherTestCase;
use Symfony\Component\Panther\Client;
use Facebook\WebDriver\WebDriverBy as By;
use Facebook\WebDriver\Exception\TimeoutException;
use Liip\FunctionalTestBundle\Test\WebTestCase;
use Symfony\Component\Panther\PantherTestCaseTrait;
use Liip\TestFixturesBundle\Test\FixturesTrait;
use App\Repository\UserRepository;
use App\DataFixtures\UserFixtures;
use App\DataFixtures\AccountFixtures;
abstract class AbstractPantherTest extends WebTestCase{
// use trait so we can combine Liip and Panther features
use PantherTestCaseTrait; // this is the magic. Panther is now available.
// provide fixtures loading feature
use FixturesTrait;
// #var Symfony\Component\Panther\Client
protected static $client;
//Initialize the test case
function setUp():void
{
static::bootKernel();
if(self::$client === null){
self::$client = self::createPantherClient(['browser' => PantherTestCase::FIREFOX]);
$this->setExcludedDoctrineTables(["RESOURCES"]);
$this->loadFixtures([
UserFixtures::class,
]);
// retrieve the test user
$userRepository = static::$container->get(UserRepository::class);
// retrieve the test user
$testUser = $userRepository->findOneByUsername('Administrator');
// simulate $testUser being logged in
self::doLogin($testUser->getUsername(), 'xxx');
}
}
}
Answering my own question as I just found out the issue. Sometimes a good night of sleep is all you need :)
So this code is actually working. The table RESOURCES got dropped because the setUp method was redefined in the concrete class implementing the test and that redefinition included another call to loadFixtures, but for a different data class and without the exclusion :
<?php
namespace App\Tests\Controller;
use App\DataFixtures\DiscountFixtures;
class DiscountControllerWebTest extends AbstractPantherTest
{
function setUp():void
{
parent::setUp();
$this->loadFixtures([
DiscountFixtures::class,
]);
}

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

$this object not working properly when used in services in Symfony2

I've working for a while with Symfony and I started to use controllers as services. The problem is that I'm not sure if I get how the Dependency Injection works. If I print $this inside an action it works perfectly.
/**
* #Route("/testing/this")
*/
public function thisAction(Request $request)
{
var_dump($this);
return new Response();
}
Response:
object(Linkedip\WizardBundle\Controller\PaymentsController)[153]
protected 'object' => null
protected 'container' =>
object(appDevDebugProjectContainer)[198]
protected 'parameterBag' =>
object(Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag)[48]
protected 'parameters' =>
array
...
But then, I decided to make my controller a service to be used in other controllers (I want to have actions methods and service methods in one controller).
parameters:
linkedip.controller.payments.class: Linkedip\WizardBundle\Controller\PaymentsController
services:
payments.controller:
class: %linkedip.controller.payments.class%
So, I add a new method that I'll plan to use in other controllers but when I try to call $this inside the new method look what I get.
/**
* #Route("/testing/this")
*/
public function thisAction(Request $request)
{
$paymentsController = $this->get('payments.controller');
$paymentsController->service();
return new Response();
}
/**
* [SERVICE]
*/
public function service()
{
var_dump($this);
return null;
}
Response:
object(Linkedip\WizardBundle\Controller\PaymentsController)[937]
protected 'object' => null
protected 'container' => null
To solve this issue I created a setter to inject $this object directly to the controller.
/**
* [DEPENDENCY INJECTION]
*/
protected $object;
public function setObject($object) { $this->object = $object; }
And then, When I try to call one of those services I need to add an extra line setting $this.
$paymentsController = $this->get('payments.controller');
$paymentsController->setObject($this);
And in the service method, I call the object.
$em = $this->object->getDoctrine()->getManager();
This code works for me but I feel is a dirty trick. Am I doing something wrong?
[..]But then, I decided to make my controller a service to be used in other controllers (I want to have actions methods and service methods in one controller).
I don't agree with this architecture choice. You should make your own controller to let the other ones herits from it. Then, if you still need a service, you can create one.
I agree with goto, you should not mix responsibilities within one class. Also, this is mainly the cause for your problem. To answer your question:
By defining your controller as a service, you are not using the default instantiation logic for a controller, so the container will not be injected automatically. If you want this to happen, you should manually inject the container (or better: the specific services you need) from within your dependency injection config. But, again, if you plan on still using the controller in the 'regular' way, by defining routes for example, things will get REALLY messy, so I would suggest, if you are already playing with the DIC, just create a separate service and call that from within your other controller. Hope this helps.

Symfony2, FOSUserBundle, authentication with cookies disabled

How I can authentication in Symfony2 without cookies in a brouser? How can generate some like this http://some.site/hello/roman?PHPSESSID=9ebca8bd62c830d3e79272b4f585ff8f or this http://some.site/9ebca8bd62c830d3e79272b4f585ff8f/hello/roman or some other url that was always available sessionid parameter. Thank you for any help.
You have to to two things. First you must extend the session storage to get the session from the query param.
namespace Elao\BackBundle\Session;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Session\Storage\NativeFileSessionStorage;
class Storage extends NativeSessionStorage
{
public function __construct($savePath = null, array $options = array(), ContainerInterface $container)
{
$request = $container->get('request');
if ($request->query->has('sessionId')) {
$request->cookies->set(session_name(), 1); // We have to simulate this cookie, in order to bypass the "hasPreviousSession" security check
session_id($request->query->get('sessionId'));
}
return parent::__construct($savePath, $options);
}
}
Source: http://www.elao.com/blog/symfony-2/symfony-2-loading-session-from-query-param.html
The next point, should be replacing the UrlGenerator to generate every url with the session id param. A example to do this, can be found in this answer.
But as nifr in the comment said, it's not a very clean requirement.

Symfony Multiple application interaction

In symfony 1.4, how to call an action of another application from the current action?
There's a blog post about that here:
http://symfony.com/blog/cross-application-links
There's a plugin for it:
https://github.com/rande/swCrossLinkApplicationPlugin
And there are some blogposts explaining it:
http://rabaix.net/en/articles/2009/05/30/cross-link-application-with-symfony
http://rabaix.net/en/articles/2009/07/13/cross-link-application-with-symfony-part-2
(Note: both are for symfony 1.2, but they should also work in 1.4)
To route to your frontend to your backend, there are three easy steps:
1. Add to your backend configuration the following two methods
These methods read the backend routing, and use it to generate routes. You'll need to supply the link to it, since php does not know how you configured your webserver for the other application.
.
// apps/backend/config/backendConfiguration.class.php
class backendConfiguration extends sfApplicationConfiguration
{
protected $frontendRouting = null;
public function generateFrontendUrl($name, $parameters = array())
{
return 'http://frontend.example.com'.$this->getFrontendRouting()->generate($name, $parameters);
}
public function getFrontendRouting()
{
if (!$this->frontendRouting)
{
$this->frontendRouting = new sfPatternRouting(new sfEventDispatcher());
$config = new sfRoutingConfigHandler();
$routes = $config->evaluate(array(sfConfig::get('sf_apps_dir').'/frontend/config/routing.yml'));
$this->frontendRouting->setRoutes($routes);
}
return $this->frontendRouting;
}
// ...
}
2. You can link to your application in such a fashion now:
$this->redirect($this->getContext()->getConfiguration()->generateFrontendUrl('hello', array('name' => 'Bar')));
3. Since it's a bit tedious to write, you can create a helper
function link_to_frontend($name, $parameters)
{
return sfProjectConfiguration::getActive()->generateFrontendUrl($name, $parameters);
}
The sfCrossLinkApplicationPlugin does this , this, but in a bit simpler fashion, you would be able to use a syntax similar to this:
<?php if($sf_user->isSuperAdmin()):?>
<?php link_to('Edit Blog Post', '#backend.edit_post?id='.$blog->getId()) ?>
<?php endif ?>
It would be something like this:
public function executeActionA(sfWebRequest $request)
{
$this->redirect("http:://host/app/url_to_action");
}
In Symfony each application is independent from the others, so if you need to call an action of another app, you need to request it directly.
Each app is represented by one main controller (frontend, backend, webapp), this controller takes care of the delivery of each request to the corresponding action (and lots of other things like filters, etc.).
I really recommend you to read this, it would be quite more explanatory: Symfony - Inside the Controller Layer

Resources