Expected errors in Laravel Dusk - php

I am looking for the Laravel Dusk equivalent of
this->expectException(InvalidArgumentException::class);
I'm new to using Laravel Dusk (running Laravel 5.7) and cannot find a way to test for an expected error. When I run the following test.
I get the following:
There was 1 error:
1)
Tests\Browser\RegistrationTest::test_user_cannot_register_with_duplicate_email
Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity
constraint violation: 19 UNIQUE constraint failed: users.email (SQL:
insert into "users" ("name", "email", "password", "updated_at",
"created_a t") values (Eoj, joe#example.com, 654321, 2018-10-16
20:35:09, 2018-10-16 20:35:09))
Caused by PDOException: SQLSTATE[23000]: Integrity constraint
violation: 19 UNIQUE constraint failed: users.email
ERRORS! Tests: 4, Assertions: 5, Errors: 1.
public function test_user_cannot_register_with_duplicate_email()
{
User::create([
'name' => 'Eoj',
'email' => 'joe#example.com',
'password' => '654321'
]);
$this->browse(function ($browser) {
$browser->visit('/') //Go to the homepage
->clickLink('Register') //Click the Register link
->assertSee('Register') //Make sure the phrase in the argument is on the page
//Fill the form with these values
->value('#name', 'Joe')
->value('#email', 'joe#example.com')
->value('#password', '123456')
->value('#password-confirm', '123456')
->click('button[type="submit"]') //Click the submit button on the page
->assertPathIs('/register') //Make sure you are still on the register page
//Make sure you see the phrase in the argument
->assertSee("The email has already been taken");
});
}
Obviously I was expecting an error - but can't work out how to tell dusk this.
When I change my code to the following I get a different error:
1) >Tests\Browser\RegistrationTest::test_user_cannot_register_with_duplicate_email
Did not see expected text [The email has already been taken] within element [body].
Failed asserting that false is true.
I am not running any other tests at the moment.
<?php
namespace Tests\Browser;
use App\User;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\RefreshDatabase;
class RegistrationTest extends DuskTestCase
{
use RefreshDatabase;
protected function setUp()
{
parent::setUp();
foreach (static::$browsers as $browser) {
$browser->driver->manage()->deleteAllCookies();
}
}
public function test_user_cannot_register_with_duplicate_email()
{
User::create([
'name' => 'Eoj',
'email' => 'someone#example.com',
'password' => '654321'
]);
$this->browse(function ($browser) {
$browser->visit('/') //Go to the homepage
->clickLink('Register') //Click the Register link
->assertSee('Register') //Make sure the phrase in the argument is on the page
//Fill the form with these values
->value('#name', 'Joe')
->value('#email', 'someone#example.com')
->value('#password', '123456')
->value('#password-confirm', '123456')
->click('button[type="submit"]') //Click the submit button on the page
->assertPathIs('/register') //Make sure you are still on the register page
//Make sure you see the phrase in the argument
->assertSee("The email has already been taken");
});
}
}

As you can see in the error:
Tests\Browser\RegistrationTest::test_user_cannot_register_with_duplicate_email Illuminate\Database\QueryException: SQLSTATE[23000]: Integrity constraint violation: 19 UNIQUE constraint failed: users.email (SQL: insert into "users" ("name", "email", "password", "updated_at", "created_a t") values (Eoj, joe#example.com, 654321, 2018-10-16 20:35:09, 2018-10-16 20:35:09))
The values are exactly the ones that you are trying to insert here:
User::create([
'name' => 'Eoj',
'email' => 'joe#example.com',
'password' => '654321'
]);
So, you can change this e-mail to a different one:
User::create([
'name' => 'Eoj',
'email' => 'someone#example.com',
'password' => '654321'
]);
And again here to the test makes sense:
->value('#email', 'someone#example.com')
This way, the email will not be duplicated, because at some point you already have a "joe#example.com".
A good practice is to reset the database before the tests.
In tests/TestCase.php you may have something like this:
use Illuminate\Foundation\Testing\RefreshDatabase; //<--HERE
abstract class TestCase extends BaseTestCase
{
use RefreshDatabase; //<--HERE
And your test must extend this class:
use Tests\TestCase; //<--HERE
class MyTest extends TestCase { //<--HERE
That way the data you be reseted, avoiding the duplicate e-mail issue in the subsequent tests.

I think the problem is that you not waiting answer from server (you are submitting form via js, right?), and assertion fires too early. Just use waitFor or waitForText in these type of scenarios.

Related

Laravel admin update users' info, duplicate email entry when updating his own admin account information

I am working on a laravel 8 application and using spatie/laravel-permission for roles and permissions. On the admin page, I'm displaying all users which the admin can do CRUD operations on. The users list also includes his own admin account.
The problem I'm having is when updating user details. The admin can successfully update user account information for other users with validation. However, if the admin tries to edit the information of his own admin account, the validation passes but I get an SQL error :
Integrity constraint violation: 1062 Duplicate entry 'admin#email.com'
for key 'users_email_unique'
See below my UserController update method for updating user information with validation:
public function update(Request $request, User $user)
{
$edit_user_rules = array(
// ... other validation rules ...
//'email' => "required|email|unique:users,email,{{$user->id}}", //. auth()->user()->id,
'email' => ['required', 'string', 'email', Rule::unique('users')->ignore($user->id)],
// ... other validation rules ...
);
$validator = Validator::make($request->all(), $edit_user_rules);
if ($validator->fails()) {
Session::flash('failed', 'Failed to save User details!');
return redirect(route('editUser', ['user' => $user->id]))->withErrors($validator)->withInput();
} else {
$validated = $validator->validated();
$updatedUser = User::find($user)->first();
// ... other user fields ...
$updatedUser->username = $validated['username'];
$updatedUser->email = $validated['email'];
// ... other user fields ...
if ($updatedUser->save()) {
return redirect(route('allUsers'));
} else {
return redirect(route('allUsers')); // with errors
}
}
}
I've tried to use different validation rules on the email field, for example,
"required|email|unique:users,email,{{$user->id}}"
"required|email|unique:users,email," . auth()->user()->id
but none worked. So I used a validation Rule to validate the unique email field. It works fine when I try to update other users' information but I get the SQL duplicate email error when updating the admin's own account.
Would appreciate any help I can get
The error is getting passed the validation rules, but it's failing when it saves the rule. This is because you're not getting the user properly. find() automatically gets the first record, so first() is unneeded, and is actually probably pulling the wrong account. When I try User::find(3)->first() locally, I'm getting user 1 instead of user 3. Remove first() from your call.
$updatedUser = User::find($user->id);
You didn't determined which column should be unique to ignore him self.
Change your email validation line to :
'email' => ['required', 'email', Rule::unique('users', 'email')->ignore($user->id)],
Don't forget to put this like to top of your code use Illuminate\Validation\Rule; .

Queue::fake not work with Laravel5 codeception module

I write test for Laravel app with codeception and modules Laravel5, REST.
One of api test:
public function testEmailRegistration(ApiTester $I) {
...
// Not correct data
$I->sendPOST($route, [
'first_name' => (string)$this->faker->randomNumber(),
'password' => $this->faker->password(1, 7),
'email' => 'not_valid_email',
]);
$I->seeResponseCodeIs(HttpCode::UNPROCESSABLE_ENTITY);
// Correct data
\Illuminate\Support\Facades\Queue::fake();
$I->sendPOST($route, [
'first_name' => $firstName,
'password' => $password,
'email' => $email,
]);
\Illuminate\Support\Facades\Queue::assertPushed(\App\Jobs\SendEmail::class);
...
}
I send requests on incorrect and correct data and make some assertions. In addition I check, that job is present in queue.
After execute test I give error:
[Error] Call to undefined method Illuminate\Queue\SyncQueue::assertPushed()
After Queue:fake facade \Illuminate\Support\Facades\Queue must resolves to QueueFake, but in fact is still QueueManager, thus assertPushed function is undefined.
Execution of $I->sendPOST() reset call Queue::fake. It happened in laravel 5 module \Codeception\Lib\Connector\Laravel5, method doRequest.
protected function doRequest($request)
{
if (!$this->firstRequest) {
$this->initialize($request);
}
$this->firstRequest = false;
$this->applyBindings();
$this->applyContextualBindings();
$this->applyInstances();
$this->applyApplicationHandlers();
$request = Request::createFromBase($request);
$response = $this->kernel->handle($request);
$this->app->make('Illuminate\Contracts\Http\Kernel')->terminate($request, $response);
return $response;
}
Each call of doRequest except the first init app again and some configurations as Queue::fake are cleared.
One of decision is one request per test. Is there another variant to work Queue::fake when in test make more then one request?
I am not sure why the Laravel module does this but I found a work-around that allows you to use fakes:
public function someTest(ApiTester $I): void
{
// what the SomeFacade::fake method call does is basically create
// a fake object and swaps it for the original implementation in
// the app container, so the we're recreating that behavior here
// only this will be persisted even after the request is issued:
$notification_fake = new NotificationFake();
// `haveInstance` is a method from Laravel Codeception Module
// which sets an object in the app container for you:
$I->haveInstance(ChannelManager::class, $notification_fake);
// making the request
$I->sendPUT('some url', $some_payload);
// assertions
$I->canSeeResponseCodeIs(Response::HTTP_OK);
$notification_fake->assertSentToTimes($expected_user, MyNotification::class, 1);
}
note that this test method is only for illustrative purposes and it misses so of the details, hence the undefined variables and such.
Also note that I user the notification fake which get registered at Illuminate\Notifications\ChannelManager, unlike most fakes that you can register under their aliased name e.g. queue. So you have to check what is being instantiated and how to swap it on your own. You can either find this in respective service providers for each service. Most of the time it's lowercase name of the facade.

How to create an error page to a QueryException?

I'm working on a commercial application and I've created Blade files for several kinds of HTTP errors. They are placed in /resources/views/errors and are working fine for authorization (503.blade.php), page not found (404.blade.php), and so on.
I've created files for 400, 403, 404, 500 and 503, until now.
The issue is when a QueryException is thrown. In this case, "Whoops, looks like something went wrong." appears.
For example, considering that name cannot be null, when I do something like this, Laravel throws a QueryException:
User::create([
'name' => null,
'email' => 'some#email.com'
]);
The exception would be:
QueryException in Connection.php line 651: SQLSTATE[23000]: Integrity
constraint violation: 1048 Column 'name' cannot be null (SQL: insert
into users (nome, email, updated_at, created_at) values (,
some#email.com, 2018-02-09 12:10:50, 2018-02-09 12:10:50))
I don't want "Whoops, looks like something went wrong." to appear to the end user, I want to show a custom page. What kind of error file do I need to create to achieve this behavior?
Try this in your controller:
try {
User::create([
'name' => null,
'email' => 'some#email.com'
]);
} catch ( \Illuminate\Database\QueryException $e) {
// show custom view
//Or
dump($e->errorInfo);
}
To catch all Query Exceptions:
You need to customize the render() method of App\Exceptions\Handler, as stated in the Docs.
public function render($request, Exception $e)
{
if ( $e instanceof \Illuminate\Database\QueryException ) {
// show custom view
//Or
dump($e->errorInfo);
}
return parent::render($request, $exception);
}

Authenticate user on login - Laravel

I am trying to authenticate use on login using this
//Attempt to auth the user
if(! auth()->attempt(request(['email','password' => Hash::make(request('password'))])))
{
//If not redirect back
return back()->withErrors([
'message' => 'Please check your email or password'
]);
}
But I am getting this error..
QueryException in Connection.php line 647:
SQLSTATE[42S22]: Column not found: 1054 Unknown column '$2y$10$5ysCvqUiloxmtRo2comd9uaiaNkLJ0eiW6x5pDFGWESAbXr5jm5N6' in 'where clause' (SQL: select * from users where email is null and $2y$10$5ysCvqUiloxmtRo2comd9uaiaNkLJ0eiW6x5pDFGWESAbXr5jm5N6 is null limit 1)
Can please somebody help me this.
Thank you :)
This error happens because you are misusing the request() helper method.
If you do like you did:
request(['email','password' => Hash::make(request('password'))])
It will produce this array:
['email' => 'myemail#lol.com', 's9audh87h2ueahjdbas' => null]
The request() method expects an array with its values representing the name of the inputs provided in the request. So if you give it an associative array, it will ignore completely the keys and it will only grab its values.
So, to get the input from the request and use it in the authorization attempt, you have to do like this:
$userInput = request('email', 'password');
if (! auth()->attempt($userInput))
{
//do something.
}
This is true assuming that your user table has an email and password columns.
Try this:
//Attempt to auth the user
if(! auth()->attempt([
'email' => request()->get('email'),
'password' => request()->get('password')
])
{
//If not redirect back
return back()->withErrors([
'message' => 'Please check your email or password'
]);
}
a) You don't need to hash the password, Laravel does that already
b) I'm not sure what you were passing as parameters. Some type of mixed array

Prevent multiple validation errors in Symfony

$this->createFormBuilder(null, array(
'validation_constraint' => new Collection(array(
'randominput' => array(
new NotBlank(),
new Email(),
new MyCustomConstraint()
)
))
->add('randominput', 'text');
Submit result (with required attribute removed from html, with firebug):
The message from Email() constraint does not apper because inside that constraint exists a piece of code, witch I think is a clone/hardcode for NotBlank() constraint
if (null === $value || '' === $value) {
return;
}
I think the Email() constraint should be a child of NotBlank()...
I want MyCustomConstraint() to not be executed if NotBlank() founds a violation. So it will be good if will be some option, for example "breakNextConstraintExecutionOnFirstViolation" => true. So if I set 10 constraints for one field, and 3rd constraint sets a violation, then next 7 constraints will not be executed.
If that kind of logic/option does not exists in symfony2, it will be good if I can access the 'validation' service from MyCustomConstraint class and reuse existing constraints but not write hardcode for each new constraint:
class MyCustomConstraintValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
// use the validator to validate the value, not hardcode
if (count( $this->get('validator')->validateValue(
$value,
new NotBlank()
)) > 0)
{
return;
}
$this->context->addViolation('MyCustomConstraint Message...');
}
}
So my question is: What should I do to prevent multiple violation messages for one filed and do not use in every new constraint hardcode?
P.S. In my previous projects (not on symfony), I made forms showing only one error message. So user completes fields one by one and see only one error but not submitting the form and that fills every fields with red errors (and scares some users). But now at least I want to resolve this issue.

Categories