symfony test - authenticated doesnt work on new request - symfony

Good morning.
Tldr: I think that, I need for Symfony Test Client something similiar to js xhr settings: withcredentals:true.
Symfony 3.1. I have action in rest api controller:
/**
* #Rest\GET("/get-stuff")
* #Rest\View
* #Security("is_granted('IS_AUTHENTICATED_FULLY')")
*/
public function GetStuffAction($userId)
{
// get userId from session
$userId = $this->getUser()->getId();
$em = $this->getDoctrine()->getManager();
$Stuff = $em->getRepository('MyApiBundle:Stuff')->findBy(['user' => $userId]);
return $this->viewGroup($Stuff, ['stuff']);
}
And it does work correct.
Now I want Test, so I have :
class TestCase extends WebTestCase
{
public function testIndex()
{
$this->client = self::createClient();
$this->storage = new MockFileSessionStorage(__dir__.'/../../../../app/cache/test/sessions');
$this->session = new Session($this->storage);
$this->logIn($this->getUser(), new Response());
$this->client->request('GET', '/get-stuff');
$content = $this->client->getResponse()->getContent();
$this->assertEquals({"message":"Full authentication is required to access this resource.","code":0},$content);
}
public function logIn(User $user, Response $response)
{
$this->session->start();
$this->cookie = new Cookie('MOCKSESSID', $this->storage->getId());
$this->cookieJar = new CookieJar();
$this->cookieJar->set($this->cookie);
$this->token = new UsernamePasswordToken($user, 'user', 'main', $user->getRoles());
$this->session->set('_security_main', serialize($this->token));
$this->getSecurityManager()->loginUser(
$this->container->getParameter('fos_user.firewall_name'),
$user,
$response
);
$this->session->save();
}
}
And test for it gives me message: "Full authentication is required to access this resource". After method logIn() I can read session data which are connected with logged user.
How can I make to be logged during this part to not receive message about authentication?:
$this->client->request('GET', '/get-stuff');
$content = $this->client->getResponse()->getContent();
More details:
1. My test class extends WebTestCase.
2. My user is overwritten for FosUserBundle.
I suppose that is something like in javascript:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
but I don't know what.
Thank you in advance for helping solve my problem!

Ok, I was good way to do this:
public function logIn(User $user) {
$session = $this->session;
$firewallContext = 'secured_area';
$token = new UsernamePasswordToken($user, 'user', 'main', $user->getRoles());
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
}

Related

How to Log User from Functional Testing with Symfony

I use FOSUserBundle for my User Authentication
I have a controller, let's call it adminController which is reserved for User granted User::ADMIN_ROLE
Everything works fine but I have an error when I try to write my functional Test
Inside my AdminControllerTest I have a method that try to test a page that need User::ADMIN_ROLE
My testAdminAccess() method
public function testAdminAccess()
{
$session = $this->client->getContainer()->get('session');
// the firewall context defaults to the firewall name
= 'main';
$user = $this->getUserByUsername('admin#yopmail.com');
$token = new UsernamePasswordToken($user, null, $firewallContext, $user->getRoles());
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->client->getCookieJar()->set($cookie);
$this->client->followRedirects();
$crawler = $this->client->request(
'GET',
'http://localhost/admin'
);
dump($crawler);
}
I'm always redirected to my login page
How can I keep the session to access some page that's protected by a specific Role?
What I'm already tried:
http://kristiankaa.dk/symfony-authentication-controller-testing
How to log in User in Session within a Functional Test in Symfony 2.3?
https://symfony-docs-zh-cn.readthedocs.io/cookbook/testing/simulating_authentication.html
https://symfony.com/doc/2.6/cookbook/testing/simulating_authentication.html
How to programmatically login/authenticate a user?
I'm using Symfony version 3.4
The best is to login the user normally submitting the username and password like a standard user would do, I use a function like this (adapt your paths):
/**
* Log the test user for the connected tests.
*/
public function login(string $username = null, string $password = null): KernelBrowser
{
$client = static::createClient();
// Login page
$client->request('GET', '/en/login/');
$this->assertTrue($kernelBrowser->getResponse()->isOk());
// Auth
$token = $client->getContainer()->get('security.csrf.token_manager')->getToken('authenticate');
$client->request('POST', '/login_check', [
'_csrf_token' => $token,
'_username' => $username ?? 'test',
'_password' => $password ?? 'test',
'_remember_me' => 'on',
]);
$this->assertTrue($client->getResponse()->isRedirect());
$client->followRedirect();
return $client;
}

Test user connected in Symfony / PhpUnit

In my controller, I use the security.token_storage service to get the user connected like :
$client = $this->container->get('security.token_storage')->getToken()->getUser();
$username = $client->getNom()." ".$client->getPrenom();
In my functional test, I try to log in a user to retrieve it in my controller but if I debug inside, it says me that i'm anonymous :
private function logIn()
{
$client = static::createClient();
$firewallName = 'main';
$token = new UsernamePasswordToken("admin", "admin", "main", array());
static::$kernel->getContainer()->get('security.token_storage')->setToken($token);
$session = static::$kernel->getContainer()->get('session');
$session->set('_security_'.$firewallName, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$client->getCookieJar()->set($cookie);
return $client;
}
public function testIndex()
{
$client = $this->logIn();
$crawler = $client->request('GET', '/');
$login = $crawler->filter("#username_index")->text();
$this->assertEquals($login, 'ADMIN NOM Admin Prenom');
}
How i can pass my user of my test to my main code ?

Functional test using Phpunit : InvalidArgumentException: Unreachable field

I'm trying to test a form but i got unreachable field exception.
My controller's code :
class StudentController extends Controller
{
/**
* #Route("/student/new",name="create_new_student")
*/
public function newAction(Request $request){
$student = new Student();
$form = $this->createFormBuilder($student)->add('name',TextType::class)
->add('save',SubmitType::class,['label' => 'Create student'])->getForm();
$form->handleRequest($request);
if($form->isSubmitted()){
$student = $form->getData();
$name = $student->getName();
echo "Your name is ".$name;
die();
}
return $this->render(':Student:new.html.twig',['form' => $form->createView()]);
}
}
My StudentControllerTest :
class StudentControllerTest extends WebTestCase
{
public function testNew(){
$client = static::createClient();
$crawler = $client->request('POST','/student/new');
$form = $crawler->selectButton('Create student')->form();
$form['name'] = 'Student1';
$crawler = $client->submit($form);
$this->assertGreaterThan(0,$crawler->filter('html:contains("Your name is Student1")')->count());
}
}
When i run the test using phpunit i got :
InvalidArgumentException: Unreachable field "name"
I'm following the tutorial from https://symfony.com/doc/current/testing.html
You should use the $form['form_name[subject]'] syntax
public function testNew(){
$client = static::createClient();
//you should request it with GET method, it's more close to the reality
$crawler = $client->request('GET','/student/new');
$form = $crawler->selectButton('Create student')->form();
$form['form_name[name]'] = 'Student1';
// [...]
}
Try this way. Edit Test
$form = $crawler->selectButton('Create student')->form(['name' => 'Student1']);
Edit Controller:
...
$name = $student->getName();
return new Response("Your name is ". $name);
Do not kill what Symfony request.

Swiftmailer not sending mail on Symfony2 service test

I am having trouble on testing Symfony services that send notification emails.
In my NotificationService I have a function that persists the notification into the database and sends a notification email using a simple MailHelper class:
public function saveNewNotificationUser($newnotificationuser){
// Get necessary repositories
$doctrine = $this->getDoctrine();
$repo_user = $doctrine->getRepository('AppBundle:User');
$em = $doctrine->getManager();
$notificationuser = $this->getNotificationUserById($newnotificationuser['idnotificationuser']);
$user = $repo_user->findOneByIduser($newnotificationuser['iduser']);
$notification = $this->getNotificationById($newnotificationuser['idnotification']);
if ($notification && $user){
// Persist on database
$notificationuser->setDate(new \DateTime("now"));
$notificationuser->setRead(0);
$notificationuser->setUseruser($user);
$notificationuser->setVariables($newnotificationuser['variables']);
$notificationuser->setNotificationnotification($notification);
$em->persist($notificationuser);
$em->flush();
// Send notification email
// Generate notification structure
$not = $this->createNotificationStructure($newnotificationuser['idnotification'],$newnotificationuser['variables']);
// Define user's dafault language and send notification email
$languagecode = $user->getLanguagelanguage()->getCode();
$mailTo = $user->getEmail();
// Get notification next on user's default language
$text = $not["languages"][$languagecode]["language"];
$this->get('mailHelper')->sendMail("notification",$mailTo,array('body' => $text["description"]), $text["title"]);
return $notificationuser->getIdnotificationUser();
}else{
return false;
}
}
When I test the function, the database insert in done correctly but the email is never sent. Here is my test class:
private $container;
private $em;
public function setUp()
{
self::bootKernel();
$this->container = self::$kernel->getContainer();
$this->em = $this->container->get('doctrine')
->getManager();
}
public function testSaveNewNotificationUser()
{
$notificationService = $this->container->get('notificationService');
$newnotificationuser = array(
'idnotificationuser' => '99',
'iduser' => '69',
'idnotification' => '1',
'variables' => '32;12'
);
$id = $notificationService->saveNewNotificationUser($newnotificationuser);
$item = $this->em
->getRepository('AppBundle:NotificationUser')
->findByIdnotificationUser($id);
$this->assertCount(1, $item);
}
protected function tearDown()
{
parent::tearDown();
$this->em->close();
}
public function testNotificationAction()
{
$client = static::createClient();
$crawler = $client->request('GET', '/api/login/testnotif');
$mailCollector = $client->getProfile()->getCollector('swiftmailer');
// Check that an email was sent
$this->assertEquals(1, $mailCollector->getMessageCount());
$this->assertTrue($client->getResponse()->isSuccessful());
}
However, if I call SaveNewNotificationUser within a Controller Action, using the same "fake" data as used in testSaveNewNotificationUser, the email is sent (when disable_delivery set to false) and I can catch it via the mailCollector.
Am I missing anything? Am I taking a wrong approach to build the tests?

FOSUserBundle authenticate selected user

I would authenticate a user in a very direct way (FOSUserBundle, Symfony2.2). I'am trying with a trivial example, but it doesn't work:
...
use FOS\UserBundle\Controller\RegistrationController as RegController;
...
class DefaultController extends Controller{
...
public function indexAction(){
$route = 'first_set_profile';
$url = $this->container->get('router')->generate($route);
$response = new RedirectResponse($url);
$userManager = $this->get('fos_user.user_manager');
$userToLogIn = $userManager->findUserByEmail('aa#bb.com');
new RegController(authenticateUser($userToLogIn, $response));
...
}
This script is running, but it is not authenticate the user with email aa#bb.com...
Thanks
This is how you can authenticate a demo user for example programmatic:
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
public function demologinAction(Request $request)
{
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->findUserByEmail('demo#example.com');
if (!$user) {
throw $this->createNotFoundException('No demouser found!');
}
$token = new UsernamePasswordToken($user, $user->getPassword(), 'main', $user->getRoles());
$context = $this->get('security.context');
$context->setToken($token);
$router = $this->get('router');
$url = $router->generate('dashboard_show');
return $this->redirect($url);
}
The third parameter in the UsernamePasswordToken must be the firewall name.

Resources