Symfony phpunit functional test edit confirm inconsistent - php

I added some functionality where if you edit an item that somebody edited in the meantime, you go to an "edit confirm" page where you can select which changes you want to overwrite.
How it's done:
if ($request->isMethod('GET')) {
$session->set('overwriteDate', $language->getUpdatedAt()?->format('Y-m-d H:i:s'));
$session->set('language_referer', $request->headers->get('referer'));
}
$form = $this->createForm(LanguageFormType::class, $language);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
var_dump($language->getUpdatedAt());
var_dump($session->get('overwriteDate'));
if ($session->get('overwriteDate') !== $language->getUpdatedAt()?->format('Y-m-d H:i:s')) {
$session->set('overwriteItem', $language);
return $this->redirectToRoute('languages_edit_confirm', ['id' => $language->getId()]);
} else {
$baseEntityService->save($language);
return $this->redirect($session->get('language_referer'));
}
}
return $this->render('admin/language/edit.html.twig', [
'language' => $language,
'form' => $form->createView()
]);
If the overwriteDate in the session doesn't match the updatedAt from the object, it will redirect to the edit confirm page. This all works fine in the browser (tested it manually a lot, never had an issue).
However, now I try to write a functional test for this, and it's inconsistent. This is the begin of the test:
$crawler1 = $this->client->request('GET', '/admin/languages');
$crawler1 = $this->filterTable($crawler1, $originalEnglishName);
$crawler1 = $this->client->click($crawler1->filter('a.edit-language')->link());
$crawler2 = $this->client->request('GET', '/admin/languages');
$crawler2 = $this->filterTable($crawler2, $originalEnglishName);
$crawler2 = $this->client->click($crawler2->filter('a.edit-language')->link());
$form = $crawler1->selectButton('saveLanguage')->form();
$form['language_form[abbreviation]'] = $firstEditAbbreviation;
$form['language_form[englishName]'] = $firstEditEnglishName;
$form['language_form[name]'] = $firstEditName;
$form['language_form[flag]'] = $firstEditFlag;
$form2 = $crawler2->selectButton('saveLanguage')->form();
$form2['language_form[abbreviation]'] = $secondEditAbbreviation;
$form2['language_form[englishName]'] = $secondEditEnglishName;
$form2['language_form[name]'] = $secondEditName;
$form2['language_form[flag]'] = $secondEditFlag;
$this->client->submit($form);
$this->client->submit($form2);
$crawler2 = $this->client->followRedirect();
$tbody = $crawler2->filter('table#difference-table tbody')->first();
After this I try to assert some stuff from $tbody however sometimes the test works fine but sometimes it gives errors because it acts like a normal edit and redirects to the homepage and the data from $tbody I search on doesn't exist.
Edit:
I tried changing the session stuff to putting it in the form as hidden input, but this gives the same issues.

You'll need to preserve the session cookie between then requests in your test(s).
The Symfony HTTPClient does not do this by default.
The HTTP client provided by this component is stateless but handling cookies requires a stateful storage (because responses can update cookies and they must be used for subsequent requests). That's why this component doesn't handle cookies automatically.
You can either handle cookies yourself using the Cookie HTTP header or use the BrowserKit component which provides this feature and integrates seamlessly with the HttpClient component.
Example usage:
// Request using the client
$crawler = $client->request('GET', '/');
// Get the cookie Jar
$cookieJar = $client->getCookieJar();
// Get the history
$history = $client->getHistory();
// Get a cookie by name
$sessionCookie = $cookieJar->get('PHPSESSID');
// create cookies and add to cookie jar
$cookie = new Cookie('PHPSESSID', 'XYZ', strtotime('+1 day'));
$cookieJar = new CookieJar();
$cookieJar->set($cookie);
// create a client and set the cookies
$client = new Client([], null, $cookieJar);

Related

Session getting destroyed after cashfree payment gateway redirecting in laravel 8 (Mozilla browser)

I am trying to integrate the Cashfree payment gateway in my Laravel 8 project. The only issue I face is in the callback URL, where an active session is automatically destroyed after getting the post data from Payment Gateway. I have also added the CSRF exception to Middleware. I have added 'secure' => env('SESSION_SECURE_COOKIE', false) & 'same_site' => null.
I have tried with a redirect()->away($payment_link), iFrame tag, and form submits directly to payment link but still getting the same issue.
The session is getting destroyed in the Mozilla browser, but it works fine in the chrome browser.
Controller (Generate Payment Request, URL, and Process Callback)
class PaymentController extends Controller
{
public function credits_add()
{
AuthCheck();
$this->data['page_name'] = 'Add Credits';
return view('merchant.payment.add_credits', $this->data);
}
public function credits_save(Request $request)
{
$request->validate([
'credit_amount' => 'required',
'credit_transaction_type' => 'required'
]);
if (!empty($request->input('credit_transaction_type')) && $request->input('credit_transaction_type') == 'Cashfree') {
$cashfreeDetails = $this->pay_with_cashfree($request);
if (!empty($cashfreeDetails) && !empty($cashfreeDetails['paymentLink'])) {
return Redirect::to($cashfreeDetails['paymentLink']);
} else {
return redirect('credits/add')->with('errorMessage', 'Sorry! Your transaction has failed.');
}
}
return redirect('credits/add');
}
public function pay_with_cashfree($request)
{
$order = new Order();
$od["orderId"] = "ORDER-84984941";
$od["orderAmount"] = 10000;
$od["orderNote"] = "Subscription";
$od["customerPhone"] = "9000012345";
$od["customerName"] = "Test Name";
$od["customerEmail"] = "test#cashfree.com";
$od["returnUrl"] = route('CreditsSuccess');
$od["notifyUrl"] = route('CreditsSuccess');
$order->create($od);
$linkArray = $order->getLink($od['orderId']);
$detailsArray = $order->getDetails($od['orderId']);
if (!empty($order) && !empty($linkArray) && !empty($linkArray->status) && $linkArray->status == 'OK') {
return array(
'paymentLink' => $linkArray->paymentLink,
'paymentDetails' => $detailsArray
);
} else {
return array();
}
}
public function credits_success(Request $request)
{
$orderId = $request->orderId;
$orderAmount = $request->orderAmount;
$referenceId = $request->referenceId;
$txStatus = $request->txStatus;
$paymentMode = $request->paymentMode;
$txMsg = $request->txMsg;
$txTime = $request->txTime;
$signature = $request->signature;
if ($txStatus == 'SUCCESS') {
return redirect('credits/add')->with('successMessage', $txMsg);
} else {
return redirect('credits/add')->with('errorMessage', $txMsg);
}
}
}
OK I figured out the problem for myself.
The new versions of the browsers might be logging you out because of the new cookie policy.
References https://developers.google.com/search/blog/2020/01/get-ready-for-new-samesitenone-secure
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
Whenever the cookie is required to be sent to server, the browser sees the SameSite attribute to decide if the cookie to be sent to server or blocked. For user actions, it is sent to the server but for auto-redirects, it doesn't if SameSite is set to 'Strict' or 'Lax' (Lax is going to be the default value now).
Solution: The cookie attribute SameSite can be set to 'None' along with specifying the 'Secure' attribute to 'true'. Setting 'Secure' attribute to 'true' would require your site to run on https. Sites running with http:// protocol will not be able to set 'Secure' cookie. Please set the 'HttpOnly' attribute to 'true' for making it accessible for http requests to the server only.
You can consider the following configuration for cookie:
SESSION_SECURE_COOKIE=true
SAME_SITE=none
You can also refer below tutorial that explains the cashfree integration in laravel:
https://www.w3techpoint.com/laravel/laravel-9-cashfree-payment-gateway-integration

Getting Wordpress REST-API content data inside a plugin with embed data

I'm creating my own routes for the wodpress api. At some point I need the rest content of the post and pages, to do this i have this function:
function get_rest_content($id, $type)
{
if ($id > 0) {
$request = new WP_REST_Request('GET', '/wp/v2/'.$type.'/' . $id);
$response = rest_do_request($request)->data;
} else {
$response = null;
}
if (empty($response)) {
return new WP_Error('wpse-error',
esc_html__('No '.$type. 'found', 'wpse'),
['status' => 404]);
}
return $response;
}
$post_1 = get_rest_content(1,'posts') // give me the rest content of the post with id=1
but if I want to have the post content with embed data I change:
new WP_REST_Request('GET', '/wp/v2/'.$type.'/' . $id);
to
new WP_REST_Request('GET', '/wp/v2/'.$type.'/' . $id . '?_embed=true');
but this new request returns rest_no_route error
I have read the source code and now understand. The second parameter of new WP_REST_Request() is the route only without query parameters. The query parameters are specified in another method. E.g.,
$request = new WP_REST_Request( 'GET', 'wp/v2/posts/999' );
$request->set_query_params( [ '_embed' => '1' ] );
However, this will not work as '_embed' is a special query parameter. It is not handled by WP_REST_Server::dispatch(), which means rest_do_request() will not handle '_embed' as rest_do_request() is just a wrapper of WP_REST_Server::dispatch().
The reason '_embed' works from a URL is that URLs are processed by WP_REST_Server::serve_request() which calls WP_REST_Server::dispatch() but also calls WP_REST_Server::response_to_data() which calls WP_REST_Server::embed_links().
If you want '_embed' to work in your get_rest_content() you will need to add the code for WP_REST_Server::embed_links().
I found a Github issue but the workaround is not working for me (at least for my code+WordPress version): https://github.com/WP-API/WP-API/issues/2857
Did you try adding the embeddable links to the response?
//get the post
$response = rest_do_request($request)->get_data();
//add the embeddable links
$results_with_embed = rest_ensure_response(rest_get_server()->response_to_data( $response, true ));

send redirect and setting cookie, using laravel 5

I have written this code, that sets a cookie in client's browser, and after that must redirect the client to 'home' route,
$response = new Response();
$response->withCookie(cookie()->forever('language', $language));
$response->header('Location' , url('/home')) ;
return $response ;
the client receives these headers but the client doesn't make request for the "home" route
how should I do both, setting the cookie and redirect the user?
Why don't you do return Redirect::to('home');
Of course you can use chaining to do more things, both in L4 and L5.
L4: return Redirect::to('home')->withCookie($cookie);
L5: return redirect('home')->withCookie($cookie);
just use the javascript method of redirecting
<script>window.location = "home";
and add it to your
$response = new Response('')
like this
$response = new Response('<script> window.location = "home";</script>'); $response->withCookie(cookie()->forever('language', $language)); return $response;

Crawler + Guzzle: Accessing to form

I am using the php guzzle Client to grab the website, and then process it with the symfony 2.1 crawler
I am trying to access a form....for example this test form here
http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm
$url = 'http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm';
$client = new Client($url);
$request = $client->get();
$request->getCurlOptions()->set(CURLOPT_SSL_VERIFYHOST, false);
$request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);
$response = $request->send();
$body = $response->getBody(true);
$crawler = new Crawler($body);
$filter = $crawler->selectButton('submit')->form();
var_dump($filter);die();
But i get the exception:
The current node list is empty.
So i am kind of lost, on how to access the form
Try using Goutte, It is a screen scraping and web crawling library build on top of the tools that you are already using (Guzzle, Symfony2 Crawler). See the GitHub repo for more info.
Your code would look like this using Goutte
<?php
use Goutte\Client;
$url = 'http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm';
$client = new Client();
$crawler = $client->request('GET', $url);
$form = $crawler->selectButton('submit')->form();
$crawler = $client->submit($form, array(
'username' => 'myuser', // assuming you are submitting a login form
'password' => 'P#S5'
));
var_dump($crawler->count());
echo $crawler->html();
echo $crawler->text();
If you really need to setup the CURL options you can do it this way:
<?php
$url = 'http://de.selfhtml.org/javascript/objekte/anzeige/forms_method.htm';
$client = new Client();
$guzzle = $client->getClient();
$guzzle->setConfig(
array(
'curl.CURLOPT_SSL_VERIFYHOST' => false,
'curl.CURLOPT_SSL_VERIFYPEER' => false,
));
$client->setClient($guzzle);
// ...
UPDATE:
When using the DomCrawler I often times get that same error. Most of the time is because I'm not selecting the correct element in the page, or because it doesn't exist. Try instead of using:
$crawler->selectButton('submit')->form();
do the following:
$form = $crawler->filter('#signin_button')->form();
Where you are using the filter method to get the element by id if it has one '#signin_button' or you could also get it by class '.signin_button'.
The filter method requires The CssSelector Component.
Also debug your form by printing out the HTML (echo $crawler->html();) and ensuring that you are actually on the right page.

Guzzle cookies handling

I'm building a client app based on Guzzle. I'm getting stucked with cookie handling. I'm trying to implement it using Cookie plugin but I cannot get it to work. My client application is standard web application and it looks like it's working as long as I'm using the same guzzle object, but across requests it doesn't send the right cookies. I'm using FileCookieJar for storing cookies. How can I keep cookies across multiple guzzle objects?
// first request with login works fine
$cookiePlugin = new CookiePlugin(new FileCookieJar('/tmp/cookie-file'));
$client->addSubscriber($cookiePlugin);
$client->post('/login');
$client->get('/test/123.php?a=b');
// second request where I expect it working, but it's not...
$cookiePlugin = new CookiePlugin(new FileCookieJar('/tmp/cookie-file'));
$client->addSubscriber($cookiePlugin);
$client->get('/another-test/456');
You are creating a new instance of the CookiePlugin on the second request, you have to use the first one on the second (and subsequent) request as well.
$cookiePlugin = new CookiePlugin(new FileCookieJar('/tmp/cookie-file'));
//First Request
$client = new Guzzle\Http\Client();
$client->addSubscriber($cookiePlugin);
$client->post('/login');
$client->get('/test/first');
//Second Request, same client
// No need for $cookiePlugin = new CookiePlugin(...
$client->get('/test/second');
//Third Request, new client, same cookies
$client2 = new Guzzle\Http\Client();
$client2->addSubscriber($cookiePlugin); //uses same instance
$client2->get('/test/third');
$cookiePlugin = new CookiePlugin(new FileCookieJar($cookie_file_name));
// Add the cookie plugin to a client
$client = new Client($domain);
$client->addSubscriber($cookiePlugin);
// Send the request with no cookies and parse the returned cookies
$client->get($domain)->send();
// Send the request again, noticing that cookies are being sent
$request = $client->get($domain);
$request->send();
print_r ($request->getCookies());
Current answers will work if all requests are done in the same user request. But it won't work if the user first log in, then navigate through the site and query again later the "Domain".
Here is my solution (with ArrayCookieJar()):
Login
$cookiePlugin = new CookiePlugin(new ArrayCookieJar());
//First Request
$client = new Client($domain);
$client->addSubscriber($cookiePlugin);
$request = $client->post('/login');
$response = $request->send();
// Retrieve the cookie to save it somehow
$cookiesArray = $cookiePlugin->getCookieJar()->all($domain);
$cookie = $cookiesArray[0]->toArray();
// Save in session or cache of your app.
// In example laravel:
Cache::put('cookie', $cookie, 30);
Other request
// Create a new client object
$client = new Client($domain);
// Get the previously stored cookie
// Here example for laravel
$cookie = Cache::get('cookie');
// Create the new CookiePlugin object
$cookie = new Cookie($cookie);
$cookieJar = new ArrayCookieJar();
$cookieJar->add($cookie);
$cookiePlugin = new CookiePlugin($cookieJar);
$client->addSubscriber($cookiePlugin);
// Then you can do other query with these cookie
$request = $client->get('/getData');
$response = $request->send();

Categories