symfony WebTestCase client does not send post data - testing

I'm currently writing functionnal tests for my symfony app. I use symfony 3 (3.1.6) with phpunit 5.6.1.
Edit : As requested by Alvin Bunk, my app is not a website, it is an API that returns only JSON. As i added in the two updates below, the Symfony test client sends a proper request object with the form data but the controller of the app receives an empty object.
Here is the code i use to test my form :
public function testSaveMediaFromMediaUrl()
{
$client = static::createClient();
$crawler = $client->request('GET', '/form');
$form = $crawler->selectButton('OK')->form();
$form['mediaUrl'] = 'http://example.com';
$client->submit($form);
var_dump($client->getResponse()->getContent());
}
The correct action of my controller is called but there is nothing in the request object when the action is called from the tests suite. using a regular web browser, everything works fine. In the controller, I use $request = Request::createFromGlobals(); to create the request object
I also tried this code to post the data and I get the same result : no POST data is received in the controller.
direct post request without using the form
public function testSaveMediaFromMediaUrl()
{
$client = static::createClient();
$crawler = $client->request('POST', '/media', ['mediaUrl' => 'http://example.com']);
var_dump($crawler->html());
}
adding the data in the submit method
public function testSaveMediaFromMediaUrl()
{
$client = static::createClient();
$crawler = $client->request('GET', '/form');
$form = $crawler->selectButton('OK')->form();
$client->submit($form, ['mediaUrl' => 'http://example.com']);
var_dump($client->getResponse()->getContent());
}
Is there something I'm doing wrong ?
EDIT:
Here is the dump of the request object I get in the controller action.
.object(Symfony\Component\HttpFoundation\Request)#1047 (21) {
["attributes"]=>
object(Symfony\Component\HttpFoundation\ParameterBag)#1050 (1) {
["parameters":protected]=>
array(0) {
}
}
["request"]=>
object(Symfony\Component\HttpFoundation\ParameterBag)#1048 (1) {
["parameters":protected]=>
array(0) {
}
}
["query"]=>
object(Symfony\Component\HttpFoundation\ParameterBag)#1049 (1) {
["parameters":protected]=>
array(0) {
}
}
["server"]=>
object(Symfony\Component\HttpFoundation\ServerBag)#1053 (1) {
["parameters":protected]=>
array(35) {[...]}
}
["files"]=>
object(Symfony\Component\HttpFoundation\FileBag)#1052 (1) {
["parameters":protected]=>
array(0) {
}
}
["cookies"]=>
object(Symfony\Component\HttpFoundation\ParameterBag)#1051 (1) {
["parameters":protected]=>
array(0) {
}
}
["headers"]=>
object(Symfony\Component\HttpFoundation\HeaderBag)#1054 (2) {
["headers":protected]=>
array(0) {
}
["cacheControl":protected]=>
array(0) {
}
}
["content":protected]=>
NULL
["languages":protected]=>
NULL
["charsets":protected]=>
NULL
["encodings":protected]=>
NULL
["acceptableContentTypes":protected]=>
NULL
["pathInfo":protected]=>
NULL
["requestUri":protected]=>
NULL
["baseUrl":protected]=>
NULL
["basePath":protected]=>
NULL
["method":protected]=>
NULL
["format":protected]=>
NULL
["session":protected]=>
NULL
["locale":protected]=>
NULL
["defaultLocale":protected]=>
string(2) "en"
}
Edit-2:
Here is the dump of the request object sent by the client (in the test case : var_dump($client->getRequest()->request);) :
object(Symfony\Component\HttpFoundation\ParameterBag)#753 (1) {
["parameters":protected]=>
array(4) {
["mediaUrl"]=>
string(41) "http://example.com"
["url"]=>
string(0) ""
["token"]=>
string(0) ""
["sizes"]=>
string(0) ""
}
}
The "test browser" seems sends the form data to the app...

Problem solved :
In my controller, I used $request = Request::createFromGlobals() as written in the doc. I removed this line and added $request as a parameter of the controller action and now the request contains the POST data sent by the test client.
My action is now defined like this :
public function generateAction(Request $request) {
// no more $request = Request::createFromGlobals();
$request->request->get('mediaUrl'); // contains data
}

usually a post request to a form return a redirect, if so check that the redirect is the correct response and follow the redirection, as example:
$client->submit($form);
$this->assertTrue($client->getResponse()->isRedirect());
$this->crawler = $client->followRedirect();
Hope this help
EDIT:
Another way could be:
$form = $crawler->selectButton('OK')->form(array(
'mediaUrl' => 'http://example.com')
);
$client->submit($form);

Related

Laravel /broadcasting/auth Always forbidden with 403 Error

I tried many solutions but no one works for me
I've installed Laravel echo and pusher js and Pusher/Pusher
#bootstrap.js
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
encrypted: true,
});
#.env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=my_id
PUSHER_APP_KEY=my_key
PUSHER_APP_SECRET=my_secret
PUSHER_APP_CLUSTER=eu
my event file NewMessage
class NewMessage implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('messages.'. $this->message->to);
}
public function broadcastWith()
{
return ["message" => $this->message];
}
}
channel.php
Broadcast::channel('messages.{id}', function ($user, $id) {
return $user->id === (int) $id;
});
Vue App JS code
mounted(){
Echo.private(`messages${this.user.id}`)
.listen('NewMessage', (e) => {
this.handleIncoming(e.message)
});
},
methods:{
saveNewMessage(msg){
this.messages.push(msg);
},
handleIncoming(message){
if(this.selectedContact && message.from == this.selectedContact.id ){
this.saveNewMessage(message);
return;
}
alert(message.text);
}
}
Api.php
Route::post('/conversation/send', 'Api\ContactController#sendNewMessage');
Contact Controller
public function sendNewMessage(Request $request)
{
$message = Message::create([
'from' => $request->sender_id,
'to' => $request->receiver_id,
'text' => $request->text
]);
broadcast(new NewMessage($message));
return response()->json($message);
}
I also read the official documentation everything is going good but I didn't figure out why, it's a throwing error. Have any idea?
I figure out why it is every time shows auth forbidden or doesn't display auth
Solution:
you need to double-check your PUSHER_APP_KEY because if it is not set correctly, it will through error because our stream not connected with pusher
PUSHER_APP_KEY="PUT KEY HERE"
If you are very sure that your app key is correct then go to the Network tab and click on your pusher app key which like e70ewesdsdssew0
If it is displaying the result like this
{"event":"pusher:connection_established","data":"{\"socket_id\":\"131139.31305364\",\"activity_timeout\":120}"}
your API key is good
if it not correct it will display an error like this
{"event":"pusher:error","data":{"code":4001,"message":"App key 3fdsfdfsdfsd not in this cluster. Did you forget to specify the cluster?"}}
Also, don't forget to put the cluster key
PUSHER_APP_CLUSTER=eu

Yii2 - activeform ajax validation with normal form submit

Using the beforeSubmit function of yii.activeForm.js, how can I make it perform a normal form submit when validation passes?
I have tried the following:
$('.ajax-form').on('beforeSubmit', function (event) {
var form = $(this);
var url = form.attr('action');
var type = form.attr('method');
var data = form.serialize();
$.ajax({
url: url,
type: type,
data: data,
success: function (result) {
if (result.errors.length != 0) {
form.yiiActiveForm('updateMessages', result.errors, true);
}
else if (result.confirmed == true) {
$('.confirm-panel').show();
}
else {
return true;
}
},
error: function() {
alert('Error');
}
});
// prevent default form submission
return false;
});
Controller:
public function actionProcess()
{
$model = $this->findModel($id);
if (Yii::$app->request->isAjax) {
$return_array = [
'errors' => [],
'confirmed' => false,
];
Yii::$app->response->format = Response::FORMAT_JSON;
$return_array['errors'] = ActiveForm::validate($model);
if ($model->confirm == 1) {
$return_array['confirmed'] = true;
}
return $this->asJson($return_array);
}
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['success']);
}
return $this->render('process', [
'model' => $model,
]);
}
As you can see I am also trying to return additional data in my AJAX response. The problem I am having is the return true in the ajax success isn't working. I can't seem to break out of the function. I have also tried form.submit() here but this just does a submit loop via AJAX.
By the way I am not using enableAjaxValidation because I have some additional custom validation that happens in my controller. So this is why I have created my own custom handler for this.
First of all, you can't return true from within an ajax success function to continue form submission as it is javascript and the last line return true is already executed before the response is received so the form ain't going to submit by returning true inside the success function.
You need to use the event afterValidate if you want to submit your page manually after successful ajax validation rather than using beforeSubmit as it will go into an infinite loop if you try to submit the form using $("form").submit() inside the ajax success function. so change your line
$('.ajax-form').on('beforeSubmit', function (event) {
to
$('.ajax-form').on('afterValidate', function (event) {
and then change your success function to
success: function (result) {
if (result.errors.length != 0) {
form.yiiActiveForm('updateMessages', result.errors, true);
}
else if (result.confirmed == true) {
$('.confirm-panel').show();
}
else {
form.submit();
}
},
Hope it helps you out.
Input validation should be made in models no matter if it's built in or custom. Then you can easily use the default ajax validation.
For creating custom validators check http://www.yiiframework.com/doc-2.0/guide-input-validation.html#creating-validators
you need not to write any such code for this propose. Yii can handle ajax validations it-self. only thing that you need to do is enable it in active form like.
php $form = ActiveForm::begin([
'id' => 'contact-form',
'enableAjaxValidation' => true,
]); ?>
and placing this code in controller after initialize $model.
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = Response::FORMAT_JSON;
return = ActiveForm::validate($model);
}

Make an ajax request from a Prestashop module

I am making a module and I need to make an ajax request, with JSON response if possible, how can i do this ?
I don't understand really well the structure of Prestashop 1.7 on this.
Thanks !
This is pretty simple, you just have to make the controller with Prestashop's standards then link it to your frontend Javascript.
Name a php file like this : ./modules/modulename/controllers/front/ajax.php
Then put inside :
<?php
// Edit name and class according to your files, keep camelcase for class name.
require_once _PS_MODULE_DIR_.'modulename/modulename.php';
class ModuleNameAjaxModuleFrontController extends ModuleFrontController
{
public function initContent()
{
$module = new ModuleName;
// You may should do some security work here, like checking an hash from your module
if (Tools::isSubmit('action')) {
// Usefull vars derivated from getContext
$context = Context::getContext();
$cart = $context->cart;
$cookie = $context->cookie;
$customer = $context->customer;
$id_lang = $cookie->id_lang;
// Default response with translation from the module
$response = array('status' => false, "message" => $module->l('Nothing here.'));
switch (Tools::getValue('action')) {
case 'action_name':
// Edit default response and do some work here
$response = array('status' => true, "message" => $module->l('It works !'));
break;
default:
break;
}
}
// Classic json response
$json = Tools::jsonEncode($response);
echo $json;
die;
// For displaying like any other use this method to assign and display your template placed in modules/modulename/views/template/front/...
// Just put some vars in your template
// $this->context->smarty->assign(array('var1'=>'value1'));
// $this->setTemplate('template.tpl');
// For sending a template in ajax use this method
// $this->context->smarty->fetch('template.tpl');
}
}
?>
In your Module Hooks, you need to bring access to the route in JS, so we basicaly make a variable :
// In your module PHP
public function hookFooter($params)
{
// Create a link with the good path
$link = new Link;
$parameters = array("action" => "action_name");
$ajax_link = $link->getModuleLink('modulename','controller', $parameters);
Media::addJsDef(array(
"ajax_link" => $ajax_link
));
}
On the frontend side, you just call it like this in a JS file (with jQuery here) :
// ajax_link has been set in hookfooter, this is the best way to do it
$(document).ready(function(){
$.getJSON(ajax_link, {parameter1 : "value"}, function(data) {
if(typeof data.status !== "undefined") {
// Use your new datas here
console.log(data);
}
});
});
And voila, you have your ajax ready to use controller

How do I define the response to a missing claim in NancyFX?

I have a module meant to enable administrators to manage users. Of course, this module requires not only authentication but also a specific claim. I have discovered that, if you are missing the claim in question, you actually get only a blank page as a response to your request. This isn't ideal.
How can I change that?
Module code below (if anyone needs to look)...
public class UserModule : NancyModule
{
public UserModule()
: base("/users")
{
this.RequiresAnyClaim(new[] { "evil-dictator" });
Get["/"] = _ =>
{
ViewBag.UserName = Context.CurrentUser.UserName;
return Negotiate.WithView("Index");
};
// Generate an invitation for a pre-approved user
Get["/invite"] = _ =>
{
throw new NotImplementedException();
};
}
}
You can use an After hook to alter the response in case the claim is missing. Note that the response you get when you do not have the required claim has HTTP status code 403 Forbidden. Check for that in the After hook and alter the response as needed.
E.g. the following will redirect to the root - "/" - of the application, when the user does have the evil dictator claim:
public class UserModule : NancyModule
{
public UserModule()
: base("/users")
{
After += context =>
{
if (ctx.Response.StatusCode == HttpStatusCode.Forbidden)
ctx.Response = this.Response.AsRedirect("/");
}
this.RequiresAnyClaim(new[] { "evil-dictator" });
Get["/"] = _ =>
{
ViewBag.UserName = Context.CurrentUser.UserName;
return Negotiate.WithView("Index");
};
// Generate an invitation for a pre-approved user
Get["/invite"] = _ =>
{
throw new NotImplementedException();
};
}
}

Elliot Haughin API verify credentials error

I am currently building an Twitter client application for campus project using Codeigniter and Elliot Haughin Twitter library. It's just a standard application like tweetdeck. After login, user will be directed to the profile page containing timline. I am using Jquery to refresh the timeline every 20 second. At the beginning, everything run smoothly until i found the following error at the random time :
![the error][1]
A PHP Error was encountered
Severity: Notice
Message: Undefined property: stdClass::$request
Filename: libraries/tweet.php
Line Number: 205
I already search the web about this error but can't find satisfied explanation. So I tried to find it myself and found that the error comes out because credentials validation error. I tried to var_dump the line $user = $this->tweet->call('get', 'account/verify_credentials'); and resulting an empty array. My question is how come this error showed up when user already login and even after updated some tweets? is there any logical error in my script or is it something wrong with the library? Could anyone explain whats happening to me? please help me...
Here's my codes:
The Constructor Login.php
<?php
class Login extends CI_Controller
{
function __construct()
{
parent::__construct();
$this->load->library('tweet');
$this->load->model('login_model');
}
function index()
{
$this->tweet->enable_debug(TRUE); //activate debug
if(! $this->tweet->logged_in())
{
$this->tweet->set_callback(site_url('login/auth'));
$this->tweet->login();
}
else
{
redirect('profile');
}
}
//authentication function
function auth()
{
$tokens = $this->tweet->get_tokens();
$user = $this->tweet->call('get', 'account/verify_credentials');
$data = array(
'user_id' => $user->id_str,
'username' => $user->screen_name,
'oauth_token' => $tokens['oauth_token'],
'oauth_token_secret' => $tokens['oauth_token_secret'],
'level' => 2,
'join_date' => date("Y-m-d H:i:s")
);
//jika user sudah autentikasi, bikinkan session
if($this->login_model->auth($data) == TRUE)
{
$session_data = array(
'user_id' => $data['user_id'],
'username' => $data['username'],
'is_logged_in' => TRUE
);
$this->session->set_userdata($session_data);
redirect('profile');
}
}
}
profile.php (Constructor)
<?php
class Profile extends CI_Controller
{
function __construct()
{
parent::__construct();
$this->load->library('tweet');
$this->load->model('user_model');
}
function index()
{
if($this->session->userdata('is_logged_in') == TRUE)
{
//jika user telah login tampilkan halaman profile
//load data dari table user
$data['biography'] = $this->user_model->get_user_by_id($this->session->userdata('user_id'));
//load data user dari twitter
$data['user'] = $this->tweet->call('get', 'users/show', array('id' => $this->session->userdata('user_id')));
$data['main_content'] = 'private_profile_view';
$this->load->view('includes/template', $data);
}
else
{
//jika belum redirect ke halaman welcome
redirect('welcome');
}
}
function get_home_timeline()
{
$timeline = $this->tweet->call('get', 'statuses/home_timeline');
echo json_encode($timeline);
}
function get_user_timeline()
{
$timeline = $this->tweet->call('get', 'statuses/user_timeline', array('screen_name' => $this->session->userdata('username')));
echo json_encode($timeline);
}
function get_mentions_timeline()
{
$timeline = $this->tweet->call('get', 'statuses/mentions');
echo json_encode($timeline);
}
function logout()
{
$this->session->sess_destroy();
redirect('welcome');
}
}
/** end of profile **/
Default.js (The javascript for updating timeline)
$(document).ready(function(){
//bikin tampilan timeline jadi tab
$(function() {
$( "#timeline" ).tabs();
});
//home diupdate setiap 20 detik
update_timeline('profile/get_home_timeline', '#home_timeline ul');
var updateInterval = setInterval(function() {
update_timeline('profile/get_home_timeline', '#home_timeline ul');
},20*1000);
//user timeline diupdate pada saat new status di submit
update_timeline('profile/get_user_timeline', '#user_timeline ul');
//mention diupdate setiap 1 menit
update_timeline('profile/get_mentions_timeline', '#mentions_timeline ul');
var updateInterval = setInterval(function() {
update_timeline('profile/get_mentions_timeline', '#mentions_timeline ul');
},60*1000);
});
function update_timeline(method_url, target)
{
//get home timeline
$.ajax({
type: 'GET',
url: method_url,
dataType: 'json',
cache: false,
success: function(result) {
$(target).empty();
for(i=0;i<10;i++){
$(target).append('<li><article><img src="'+ result[i]['user']['profile_image_url'] +'">'+ result[i]['user']['screen_name'] + ''+ linkify(result[i]['text']) +'</li></article>');
}
}
});
}
function linkify(data)
{
var param = data.replace(/(^|\s)#(\w+)/g, '$1#$2');
var param2 = param.replace(/(^|\s)#(\w+)/g, '$1#$2');
return param2;
}
That's the codes. Please help me. After all, I really appreciate all comments and explanation from you guys. Thanks
NB: sorry if i had bad English grammar :-)
You are making a call to statuses/home_timeline which is an unauthenticated call. The rate limit for unauthenticated calls is 150 requests per hour.
Unauthenticated calls are permitted 150 requests per hour.
Unauthenticated calls are measured against the public facing IP of the
server or device making the request.
This would explain why you see the problem at the peak of your testing.
With the way you have it setup you would expire your rate limit after 50 minutes or less.
I suggest changing the interval to a higher number, 30 seconds would do. That way you'll be making 120 requests per hour and under the rate limit.