I am using PHPUnit and Guzzle to make API unit test. The probleme I have is that I am not able or I can't really figure out how to persiste cookie between my test methods. Or maybe am doing it all wrong ^^
The first test testGetFirst gets the data and set a session cookie on server side. The response has a Set-Cookie header with the right cookie.
The seconde test testGetSecond should return a data set if the cookie exists. Unfortunatelly it seems that Guzzle::Client doesn't store/persiste the cookie between the methods.
use PHPUnit\Framework\TestCase;
use GuzzleHttp\Client;
class MyTest extends Testcase
{
public $guzzleClient;
/**
* #before
*/
function initVariables() {
$this->guzzleClient = new Client([
'base_uri' => 'http://apiuri.com',
'cookies' => true
]);
}
// This call get some data and set a cookie (session cookie) on server side
function testGetFirst() {
$params = [
'query' => [
'param1' => 'myparam'
]
];
$response = $this->guzzleClient->request('GET', '/', $params);
// if I print out the response headers I get my cookie in 'Set-Cookie' header
// I suppose the cookie has been set correctly
print_r($response->getHeaders());
// if I print out the client conf cookie, I get the cookie too
print_r($this->guzzleClient->getConfig('cookies')->toArray());
}
// This call get data to have access it need to use a cookie that has been set by testGetFirst
// But unfortunatelly the cookie is empty while making the request
/**
* #depends testGetFirst
*/
function testGetSecond() {
$params = [
'query' => [
'param1' => 'hello'
]
];
// if I print out the client conf cookie, cookies is empty
print_r($this->guzzleClient->getConfig('cookies')->toArray());
$response = $this->guzzleClient->request('GET', '/second', $params);
// as the request can't access to a cookie it sends an error response
}
}
I know there is CookieJar that I can use in each method and pass the Set-Cookie value in to Jar but I was hopping to avoid it.
Do you have any idea or suggestion ?
Very appreciate your help.
Due to the #before annotation for the initVariables method, this method is executed before each test. That method is creating a fresh client before each test.
To solve your particular situation, since you're using the #depends testGetFirst annotation, you could have the testGetFirst method return the client object. The testGetSecond method can then receive it as an argument. Check the Test Dependencies documentation for more info regarding passing arguments into dependent tests.
Related
I want to send a request with or without 'Token' as a header.
If request has 'Token' as a header: if the user already has that item, it will return the item with the proper item_id of a specific user (based on its token), otherwise it will return null.
If request doesn't have 'Token' as a header: it will return the item with that item_id
I'm working with Zend Framework and in ItemResource I have this method:
public function fetch($id)
{
}
How can I check if my request has Token as a header or not and implement both cases inside fetch()?
Using Laminas API Tools it depends on wether you 're using a RPC or a REST resource. I will explain which tools the Laminas API Tools give you to evaluate the received header data.
You don 't have to reinvent the wheel, because Laminas API Tools has the received headers already at hand, when you 're in your fetch method.
Representational State Transfer (REST)
Rest resources normally extend the \Laminas\ApiTools\Rest\AbstractResourceListener class. This class listens for \Laminas\ApiTools\Rest\ResourceEvent. Fortunately, this event provides you with a request object that also contains the received header data.
<?php
declare(strict_types=1);
namespace Marcel\V1\Rest\Example;
use Laminas\ApiTools\Rest\AbstractResourceListener;
class ExampleResource extends AbstractResourceListener
{
public function fetch($id)
{
// requesting for an authorization header
$token = $this->getEvent()->getRequest()->getHeader('Authorization', null);
if ($token === null) {
// header was not received
}
}
}
As you can see the ResourceEvent returns a \Laminas\Http\Request instance when calling getRequest(). The request instance already contains all request headers you 've received. Just call getHeader with the given name and as second parameter a default value, which should be returned, when the header was not set. If there is no http_token header, you 'll get null as a result.
Remote Procedure Calls (RPC)
Since RPC requests are handled with a MVC controller class, you can get the request as easy as in a rest resource. Controller classes extend from \Laminas\Mvc\Controller\AbstractActionController, which already contains a request instance.
<?php
declare(strict_types=1);
namespace Marcel\V1\Rpc\Example;
use Laminas\Mvc\Controller\AbstractActionController;
class ExampleController extends AbstractActionController
{
public function exampleAction()
{
$token = $this->getRequest()->getHeader('Authorization', null);
if ($token === null) {
// token was not set
}
}
}
As you can see getting header data in rpc requests is as easy as in resource listeners. The procedure is the same because a request instance is also used here.
Conclusion
There is absolutely no need for coding things, that are already there. Just get the request instance from the event or the abstract controller and retrieve the header you want. Always keep in mind, that there are security aspects like CRLF injections, when dealing with raw data. The Laminas framework handles all this for you already.
Additionally you can check for all received headers by calling ->getHeaders() instead of ->getHeader($name, $default). You 'll get a \Laminas\Http\Header instance with all received headers.
You can get all HTTP header values by getallheaders() or just get the specific value by $_SERVER['HTTP_XXX'], in your case, replace XXX with Token, $_SERVER['HTTP_Token'].
Manual: https://www.php.net/manual/en/reserved.variables.server.php
public function fetch($id)
{
$token = $_SERVER['HTTP_Token'];
// do your busniess code
}
I am programming an application that uses ReactJs for the front-end in localhost:3001 and Symfony for the back-end localhost:3000, and to enable cross origin communication I use "cors-bundle" in Symfony.
Now I want to create a Cookie when a user log in, but it doesn't seem to be created !
Thanks for your help,
This is the code in Symfony the moment a user logs in :
use Symfony\Component\HttpFoundation\Cookie;
$cookie = new Cookie( 'my_cookie', 1, strtotime('tomorrow') );
$res = new Response();
$res->headers->setCookie( $cookie );
return new JsonResponse($res);
This what I also tried :
$res->headers->setCookie(Cookie::create('my_cookie', 1));
return new JsonResponse($res);
What you need is:
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class YourClass extends AbstractController {
public function yourMethod(Request $request) {
$cookie = new Cookie(
"cookie_name_here", // Cookie name
1, // Cookie content
(new DateTime('now'))->modify("+1 day"), // Expiration date
"/", // Path
"localhost", // Domain
$request->getScheme() === 'https', // Secure
false, // HttpOnly
true, // Raw
'Strict' // SameSite policy
);
$res = new JsonResponse();
$res->headers->setCookie($cookie);
return $res;
}
}
Things to note here.
Setting the "Secure" flag will mean this cookie is only transmitted on a HTTPS connection. Do not neglect this in production. You may want to use $request->getScheme() === 'https' as the parameter evaluation.
The "HttpOnly" flag is for security and stops Javascipt and Browser extensions from accessing the cookie. If you're using XHR to make requests (for instance, Axios) and include "withCredentials" then this is ok to set as true and will be sent anyway. If you want to read the value in React then set this to false
It should be
$res = new JsonResponse();
$res->headers->setCookie($cookie);
Return $res;
I've not experienced any difference in doing this from Symfony 2. This has already been answered here: How to attach cookies to JSON Response in symfony2?
Have you checked your browser's developer tools to see if a cookie is arriving in the response? Specifically look at the Network tab and monitor the headers of the AJAX response.
I would like to test if my cookie is set correctly in controller.
class RedirectControllerTest extends WebTestCase
{
public function testSetCookie($lang, $id, $actionUrl)
{
$client = static::createClient();
$crawler = $client->request('GET', /test);
$jar = $client->getCookieJar();
}
}
Controller sets cookie, but my cookie jar in test case is empty.
Is it even possible to have access to cookies in test cases?
Accessing cookies through the cookie jar is the way to go:
/* #var \Symfony\Component\BrowserKit\Cookie $cookie */
$cookie = $client->getCookieJar()->get('foo');
It works in my projects. If your cookie jar is empty you're most likely not setting the cookie properly. Remember you need to return the response you set the cookie on.
I have a service that should be able to read and write cookies. To do that in a Symfony-like manner, the service must have access to the request and the response. I can imagine that it's possible to pass the request to the service through the service configuration, but I don't know how. I'm not sure how I'm going to give the service the ability to write cookies though. Any suggestions on how to do this would be appreciated.
Note: I really don't want to have to manually pass variables to the service every time I use it.
I think you really have a couple of options - it really depends on what you are trying to store in a cookie and at what point in the process you need to read do the work.
I suggest your first option is to create a service, that has access the the request and creates a response, which it returns ...
Define your service in services.yml :
services:
a_service:
class: Acme\DemoBundle\RequestServiceClass
arguments: [#request]
scope: request
Your class :
//Acme\DemoBundle\RequestServiceClass.php
class RequestServiceClass
{
private $request;
public function __construct(Request $request){
$this->request= $request;
}
public function doSomething(){
// get cookie
$value = $this->request->cookies->get('cookie');
// create cookie
$cookie = new Cookie('cookie', 'value', time() + 3600 * 24 * 7);
// create response
$response = new Response();
// set cookie in response
$response->headers->setCookie($cookie);
return $response;
}
}
Then to use your service you do something like this
public myAction()
{
$response = $this->get('a_service')->doSomething();
return $response;
}
The other way of doing it, is to create a kernel.response listener ... its done like this :
Add a service to services.yml :
services:
a_listener:
class: Acme\DemoBundle\MyListener
tags:
- { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
Your listener class looks like this :
// Acme\DemoBundle\MyListener.php
class MyListener
{
public function onKernelResponse(FilterResponseEvent $event)
{
$response = $event->getResponse();
$request = $event->getRequest();
// get cookie
$value = $request->cookies->get('cookie');
// create cookie
$cookie = new Cookie('cookie', 'value', time() + 3600 * 24 * 7);
// set cookie in response
$response->headers->setCookie($cookie);
}
}
The difference between the 2 methods is what information is available at the time of process - for example the service has access to everything you pass it ... the response listener has access to everything in the request and the response - you could check if the response is as expected (ie format or content) and then set a cookie according to that.
Some links to some useful documentation to read :
kernal.response event
HTTPKernal Component
Services Scopes
HTTP stops at the controllers and listeners of HTTP related events. A service (which is not a controller or an HTTP event listener) should not set or get cookies. Instead the controller should handle the cookie. It gets the data from cookie before consuming the service's methods which accept it as argument and return its new value or using a by-reference argument. This way your service is decoupled from HTTP and can be easily re-used ans tested.
I'm writing a functional test for an action that uses Symfony2's session service to fetch data. In my test class's setUp method, I call $this->get('session')->set('foo', 'bar');. If I output all the session data (using print_r($this->get('session')->all());) either in setUp or in the actual test method, I get back foo => bar. But if I try outputting the session data from the action being tested, I get back an empty array. Does anyone know why this is happening, and how I can prevent it?
I should note that if I call $_SESSION['foo'] = 'bar' from within setUp() the data is persisted and I can access it from the action - this problem seems local to Symfony2's session service.
Firstly try using your client's container (I'm assuming you're using WebTestCase):
$client = static::createClient();
$container = $client->getContainer();
If it still doesn't work try saving the session:
$session = $container->get('session');
$session->set('foo', 'bar');
$session->save();
I didn't try it in functional tests but that's how it works in Behat steps.
You can retrieve the "session" service.
With this service, you can :
start the session,
set some parameters into session,
save the session,
pass the Cookie with sessionId to the request
The code can be the following :
use Symfony\Component\BrowserKit\Cookie;
....
....
public function testARequestWithSession()
{
$client = static::createClient();
$session = $client->getContainer()->get('session');
$session->start(); // optional because the ->set() method do the start
$session->set('foo', 'bar'); // the session is started here if you do not use the ->start() method
$session->save(); // important if you want to persist the params
$client->getCookieJar()->set(new Cookie($session->getName(), $session->getId())); // important if you want that the request retrieve the session
$client->request( .... ...
The Cookie with $session->getId() has to be created after the start of the session
See Documentation http://symfony.com/doc/current/testing/http_authentication.html#creating-the-authentication-token