I had a small test done in PHP for a Controller I had written in Symfony2:
class DepositControllerTest extends WebTestCase {
public function testDepositSucceeds() {
$this->crawler = self::$client->request(
'POST',
'/deposit',
array( "amount" => 23),
array(),
array()
);
$this->assertEquals(
"Deposit Confirmation",
$this->crawler->filter("title")->text());
}
}
Up to here, everything was great. Problem started when I realized I wanted to disable possible re-submissions while refreshing the page. So I added a small mechanism to send nonce on every submission.
It works something like this:
class ReplayManager {
public function getNonce() {
$uid = $this->getRandomUID();
$this->session->set("nonce", $uid);
return $uid;
}
public function checkNonce($cnonce) {
$nonce = $this->session->get("nonce");
if ($cnonce !== $nonce)
return false;
$this->session->set("nonce", null);
return true;
}
}
So I had to mofidy the controller to get the nonce when displaying the form, and consume it when submitting.
But now this introduces a problem. I cant make a request to POST /deposit because I dont know what nonce to send. I thought to requesting first GET /deposit to render the form, and setting one, to use it in the POST, but I suspect Symfony2 sessions are not working in PHPUnit.
How could I solve this issue? I would not want to go to Selenium tests, since they are significant slower, not to mention that I would have to rewrite A LOT of tests.
UPDATE: I add a very simplified version of the controller code by request.
class DepositController extends Controller{
public function formAction(Request $request){
$this->replayManager = $this->getReplayManager();
$context["nonce"] = $this->replayManager->getNonce();
return $this->renderTemplate("form.twig", $context);
}
protected function depositAction(){
$this->replayManager = $this->getReplayManager();
$nonce = $_POST["nonce"];
if (!$this->replayManager->checkNonce($nonce))
return $this->renderErrorTemplate("Nonce expired!");
deposit($_POST["amount"]);
return $this->renderTemplate('confirmation.twig');
}
protected function getSession() {
$session = $this->get('session');
$session->start();
return $session;
}
protected function getReplayManager() {
return new ReplayManager($this->getSession());
}
}
I'm not sure what ReplayManager does, but it looks to me as if it is not the right class to handle the 'nonce'. As the 'nonce' is ultimately stored in and retrieved from the session it should either be handled by the controller or abstracted out into its own class which is then passed in as a dependency. This will allow you to mock the nonce (sounds like a sitcom!) for testing.
In my experience problems in testing are actually problems with code design and should be considered a smell. In this case your problem stems from handling the nonce in the wrong place. A quick refactoring session should solve your testing problems.
It is possible to access the Symfony2 session from PHPUnit via the WebTestCase client. I think something like this should work:
public function testDepositSucceeds() {
$this->crawler = self::$client->request(
'GET',
'/deposit',
);
$session = $this->client->getContainer()->get('session');
$nonce = $session->get('nonce');
$this->crawler = self::$client->request(
'POST',
'/deposit',
array("amount" => 23, "nonce" => $nonce),
array(),
array()
);
$this->assertEquals(
"Deposit Confirmation",
$this->crawler->filter("title")->text());
}
EDIT:
Alternatively, if there is a problem getting the nonce value from the session, you could try replacing the two lines between the GET and POST requests above with:
$form = $crawler->selectButton('submit');
$nonce = $form->get('nonce')->getValue(); // replace 'nonce' with the actual name of the element
Related
I need a clean solution to set data after submit a page from being populated by :
$form->loadDataFrom( $Page );
There is my code :
public function FormUpdate() {
$error="Required";
$fields = new FieldList(
TextField::create('Title', 'Title')->setCustomValidationMessage($error),
TextField::create('Description', 'Description')->setCustomValidationMessage($error),
TextField::create('Subject', 'Description')->setCustomValidationMessage($error),
);
$actions = new FieldList(
FormAction::create("FormUpdateSubmit")->setTitle('Update')
);
$Page=Versioned::get_by_stage('Page', 'Live')->filter( array('SecureCode' => $_REQUEST['id'] ))->First();
$fields->push( HiddenField::create('id','SecureCode', $Page->SecureCode ));
$fields->push( CheckboxField::create('Approbation', "Approbation")->setCustomValidationMessage($error) ); ),
$required = new RequiredFields(array(
'Title','Subject','Description'
));
$form = new Form($this, 'FormModifier', $fields, $actions, $required);
$form->loadDataFrom( $Page );
$form->setAttribute('novalidate', 'novalidate');
return $form;
}
The problem... If I change Title and Description and I empty Subject field, i'm redirected back to the form page with the error message below Subject but, All fields are reloaded from $form->loadDataFrom($Page); That wasn't good. I must prevent that data to be reloaded. In this case, datas posted must replace $Page. What I have missing?
I generally use loadDataFrom on the action that called the form (rather than inside the form function). So for example:
...
public function index()
{
$form =$this->Form();
$form->loadDataFrom($this);
$this->customise(array("Form" => $form));
return $this->renderWith("Page");
}
...
That way the function only returns the base form and you alter it as and when required.
Your form will be called once when adding it in the template, and once via request. Since all actions on a controller get the request as parameter, you can modify your form function like so:
public function FormUpdate($request = null) {
Then inside your function, only populate the form if it's not called via a request, eg.
if (!$request) {
$form->loadDataFrom($Page);
}
I've developed a CI site on my local machine using WAMP. I'm using CI 3 with the HMVC extension and it all works fine.
I've just uploaded the site to the production server and changed the config files etc to get it working. However, form validation callbacks are not working on the production server.
Here's an example from a login form:
// Process login form
public function submit()
{
$this->load->library('form_validation');
$username = $this->input->post('username', TRUE);
$this->form_validation->set_rules('username', 'Username', 'required');
$this->form_validation->set_rules('password', 'Password', 'required|callback_do_login');
if($this->form_validation->run($this) == FALSE)
{
$this->login();
}
else
{
// Set login session
...
}
}
This is the callback function do_login:
public function do_login($password)
{
// Get user / pass from POST
$submitted_password = $this->input->post('password', TRUE);
$username = $this->input->post('username', TRUE);
$this->crud->setTable($this->table);
$this->load->model('mdl_users');
// Check User Exists
$query = $this->mdl_users->getUser($username);
if($query->num_rows() == 1)
{
// Get stored password
$row = $query->row();
$stored_password = $row->password;
// Check password
$this->load->module('mod_security');
$result = $this->mod_security->login($username, $submitted_password, $stored_password); // Returns false if no match
return ($result) ? TRUE : FALSE;
}
else
{
return FALSE;
}
}
On my local WAMP setup, this all works fine. But on the server
$this->form_validation->run($this)
always returns false.
To eliminate any errors with the callback function itself, I changed it to the following as a test:
public function do_login($password)
{
return TRUE;
}
... which will obviously always return TRUE, however when $this->form_validation->run($this) is called, even though I changed the do_login callback function to return TRUE, $this->form_validation->run($this) still returns FALSE!!
This is driving me crazy and I have no idea why this is happening. It is as if the form validation is ignoring the callback function and just returning false.
Can anyone help me? Could it be a setting on the server causing it or something else that could have changed when I uploaded the site files to the server?
If it is relevant, on my local machine, within the do_login callback function I had originally set the callback error message like this:
$this->form_validation->set_message('do_login', 'User / Pass Incorrect');
...which worked fine, but on the production server it threw an error stating: "Unable to access an error message corresponding to your field name Password.(do_login)". I had to set the error message in the language library file to overcome this. But the fact that this happened on the production server and not on my WAMP setup makes me think there must be some setting or something on the server that is causing this.
Anyway, any help is gratefully received.
Thanks
I finally found the solution. The HMVC application requires a small library file named as MY_Form_validation.php. Seemingly everywhere on the net this file is shown to be thus:
class MY_Form_validation extends CI_Form_validation {
function run($module = '', $group = '')
{
(is_object($module)) AND $this->CI = &$module;
return parent::run($group);
}
}
However, although this worked in my WAMP setup, it did not work on my server. I found that by declaring the CI variable at the start the server problem is resolved. So the MY_Form_validation.php file finally becomes:
class MY_Form_validation extends CI_Form_validation {
public $CI;
function run($module = '', $group = '')
{
(is_object($module)) AND $this->CI = &$module;
return parent::run($group);
}
}
I created a form request class and defined a bunch of rules. Now I would like to test these rules to see if the behaviour meets our expectations.
How could I write a test to accomplish that?
Many thanks in advance for your answers!
Update: more precisely, I would like to write a unit test that would check e.g. if a badly formatted email passes validation or not. The problem is that I don't know how to create a new instance of the Request with fake input in it.
The accepted answer tests both authorization and validation simultaneously. If you want to test these function separately then you can do this:
test rules():
$attributes = ['aa' => 'asd'];
$request = new MyRequest();
$rules = $request->rules();
$validator = Validator::make($attributes, $rules);
$fails = $validator->fails();
$this->assertEquals(false, $fails);
test authorize():
$user = factory(User::class)->create();
$this->actingAs($user);
$request = new MyRequest();
$request->setContainer($this->app);
$attributes = ['aa' => 'asd'];
$request->initialize([], $attributes);
$this->app->instance('request', $request);
$authorized = $request->authorize();
$this->assertEquals(true, $authorized);
You should create some helper methods in base class to keep the tests DRY.
You need to have your form request class in the controller function, for example
public function store(MyRequest $request)
Now create HTML form and try to fill it with different values. If validation fails then you will get messages in session, if it succeeds then you get into the controller function.
When Unit testing then call the url and add the values for testing as array. Laravel doc says it can be done as
$response = $this->call($method, $uri, $parameters, $cookies, $files, $server, $content);
Here's a full example of testing validation:
use App\Http\Requests\PostRequest;
use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
class PostRequestTest extends TestCase
{
protected function newTestRequest($data = [])
{
$request = new PostRequest();
$request->initialize($data);
return $request
->setContainer($this->app)
->setRedirector($this->app->make(Redirector::class));
}
public function testValidationFailsWhenEmptyTitleIsGiven()
{
$this->expectException(ValidationException::class);
$this->newTestRequest(['title' => ''])->validateWhenResolved();
}
}
I'm new in Laravel and some times it's hard to undestand waht's happening.
I have a code for validate a form. If I use it in a function it works right, but dosn't work the Redirect:back() if a call it from a other function. I want to reuse the code for all forms, but can't refactor.
This is the code (not working, does't redirect, continue executing code if validation fails)
private function formValidation($data ,$rules ,$error_name = 'user', $message = 'Hay errores en el formulario')
{
$validator = Validator::make($data, $rules);
if ($validator->fails())
{
Session::Flash('message' , $message);
return Redirect::back()->withErrors($validator,$error_name)->withInput();
}
else
return $validator;
}
public function loginPost()
{
$this->formValidation(Input::all(),User::$rules_login);
..........
This code works fine, it redirects if validation fails:
public function loginPost()
{
$validator = Validator::make(Input::all(),User::$rules_login);
if ($validator->fails())
{
Session::Flash('message' , 'Hay errores en el formulario');
return Redirect::back()->withErrors($validator,'user')->withInput();
}
.............
Can anybody help me plese? Really I want to call it from another class, but after see that does not work I call fron a private function.
I try calling Redirect:to('login', $data, $rules...) and with Redirect::route too, still not working.
Tanks.
This works, so I can refactor ;)
private function formValidation($data ,$rules , $message = 'Hay errores en el formulario')
{
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
MyHelpers::sendSessionFlashMessages(array('message' => $message)); // Send a Session::flash with css information
return Redirect::back()->withErrors($validator, 'user')->withInput();
}
return $validator;
}
public function loginPost()
{
if (get_class($response = $this->formValidation(Input::all(),User::$rules_login)){ === 'Illuminate\Http\RedirectResponse')
return $response;
}
...................... rest of the function
Laravel uses the return value of the loginPost() method to determine what the response to the browser is. However, that method doesn't return the redirect - formValidation() does. So what happens is that formValidation returns a redirect, and then it gets ignored completely. If you put the redirect in the loginPost() method, it should work straight away.
I was writing a simple login form, everything works fine (validation etc.) but I can't get the values, there's my code:
public function executeIndex(sfWebRequest $request)
{
$this->getUser()->clearCredentials();
$this->getUser()->setAuthenticated(false);
$this->form = new LoginForm();
if ($request->isMethod('post') && $request->hasParameter('login')) {
$this->form->bind($request->getParameter('login'));
if ($this->form->isValid()) {
$this->getUser()->setAuthenticated(true);
$this->getUser()->addCredential('user');
$this->login = $this->form->getValue('login');
}
}
}
$this->login is NULL. Now I checked almost everything, the form is valid, isBound() is true, count() returns 3, I can see the values in my request:
parameterHolder:
action: index
login: { login: foo, password: foo, _csrf_token: 53ebddee1883d7e3d6575d6fb1707a15 }
module: login
BUT getValues() returns NULL, getValue('login') etc. returns NULL as well. How can it be?
And no, I don't want to use sfGuard-Plugins ;)
What about trying something like this
$form['value_name']->getValue()
Is it still NULL?
Also is it possible that you created a custom post validator?
Callback validation must return values back to caller:
return $values;