Laravel: Feature Test fails because of added middleware - laravel

After a user signs up and verifies their email, they must complete their signup with additional information. This happens at /register/complete-signup
The issue makes absolutely no sense to me.
For whatever reason, when I added my Middleware has-not-completed-signup, the test starts failing because a App\User no longer has the associated App\Account which is happening in the controller via attach()
As soon as I remove my middleware from the route, it works fine.
My middleware is there to prevent a user who has completed the signup already from visiting or POSTing to those routes. I tested in the browser and the redirect works. The controller method is being used in the test and i can dd($account->users) and get the correct response. But if I do $user->accounts, the collection is empty.
Once I remove my middleware, $user->accounts is no longer empty. But I did a dd() inside my middleware and it's not even running (which is correct because the user doesn't have an account).
So why would this make it fail? I'm completely lost.
I tried to include all relevant information below. If there is something else you need, please let me know.
Edit:
In my middleware, I've commented out the functionality. Something about checking an eloquent relationship makes me test fail. I have no idea why.
This makes the test fail:
if (!auth()->user()->accounts->isEmpty()) {
//return redirect(RouteServiceProvider::HOME);
}
If for example I change it to something useless like this, it works:
if (auth()->user()) {
//return redirect(RouteServiceProvider::HOME);
}
I can do $account->users , but $user->accounts returns empty collection on the controller when I use my middleware
Original:
Here are my routes:
// auth scaffolding
Auth::routes(['verify' => true]);
// main app routes
Route::middleware('verified', 'auth')->group(function() {
// User verified and has an App\Account
Route::middleware('completed-signup')->group(function() {
Route::get("/", 'HomeController#index' )->name('home');
Route::get('/paywall', 'BillingController#paywall')->name('paywall');
});
// The user hasn't attached an App\Account to their User
Route::middleware('has-not-completed-signup')->group(function() {
Route::get("/register/complete-signup", 'AccountController#showCompleteSignup' )->name('complete-signup');
Route::post('/register/complete-signup', 'AccountController#storeCompleteSignup')->name('complete-signup.store');
});
});
has-not-completed-signup Middleware:
public function handle($request, Closure $next)
{
if (auth()->user()->hasCompletedAccountSetup()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
App/User method:
Class User extends Authenticatable implements MustVerifyEmail {
...
public function accounts() {
return $this->belongsToMany('App\Account', 'account_role_user')->withPivot('role_id');
}
public function hasCompletedAccountSetup() {
return !$this->accounts->isEmpty();
}
...
AccountController#storeCompletedSignup:
public function storeCompleteSignup(Request $request) {
$validatedData = $request->validate([
'company' => 'required|max:255',
'contact_state' => 'required|max:255',
'contact_country' => 'required|max:255',
'contact_zip' => 'required|max:255',
'contact_city' => 'required|max:255',
'contact_phone' => 'nullable|max:255',
'contact_address_1' => 'required|max:255',
'contact_address_2' => 'nullable|max:255',
'contact_first_name' => 'required',
'contact_last_name' => 'required',
'contact_email' => 'required'
]);
$user = auth()->user();
$account = new Account($validatedData);
$account->contact_first_name = $user->first_name;
$account->contact_last_name = $user->last_name;
$account->contact_email = $user->email;
$account->save();
$account->users()->attach(
$user->id,
['role_id' => Role::where('name', 'owner')->first()->id ]
);
return $request->wantsJson()
? new Response('Signup Completed Successfully', 201)
: redirect()->route('/');
}
My Test:
/**
* #test
*/
public function a_user_can_complete_signup()
{
$user = Factory('App\User')->create();
$this->actingAs($user);
$accountAttributes = factory('App\Account')->raw([
'contact_first_name' => "TEST",
'contact_last_name' => $user->last_name,
'contact_email' => $user->email,
'contact_country' => "USA"
]);
$res = $this->post('/register/complete-signup', $accountAttributes);
$res->assertSessionDoesntHaveErrors();
$this->assertTrue( !$user->accounts->isEmpty() ); // THIS FAILS
$this->assertTrue( $user->accounts->first()->company == $accountAttributes['company']);
$this->assertTrue( $user->accounts->first()->contact_first_name == $user->first_name );
}

The issue wasn't actually with the middleware, but it was because I had to refresh the model after the POST on the test.
$this->assertTrue( !$user->accounts->isEmpty() );
needed to become
$this->assertTrue( !$user->fresh()->accounts->isEmpty() );
which passed the test.
I knew about the fresh and refresh() methods, but the middleware causing the issue didn't make sense to me.

Related

How do I run a phpunit test on Redis pub/sub?

I'm building a messenger system with Redis publishing on the Laravel end and subscribing on a node server. I would like to test what is stored in the redis pub method using PHPUnit, but I have no idea where to start.
Controller
class MessageController extends Controller
{
public function store(Conversation $conversation, Request $request)
{
$user = Auth::user();
$message = Message::create([
'body' => $request->input('message'),
'conversation_id' => $conversation->id,
'sender_id' => $user->id,
'type' => 'user_message'
]);
$redis = Redis::connection();
$data = new MessageResource($message);
$redis->publish('message', $data);
}
}
Current Test
/** #test */
public function a_user_can_send_a_message()
{
$this->actingAs($user = User::factory()->create());
$message = Message::factory()->make(['sender_id' => $user->id]);
$conversation Conversation::factory()->create();
$response = $this->json('POST', '/api/message/'. $conversation->id, ['message' => $message->body])
->assertStatus(201);
$response->assertJsonStructure([
'data' => [
'body',
'sender',
]
]);
}
Essentially what I'm trying to see is if message has been published on Redis. I'm unsure how to do this, and I think you would probably need to clear the message from Redis after, would you not?
Your test should be like this:
public function test_a_user_can_send_a_message()
{
$redisSpy = Redis::spy();
$redisSpy->shouldReceive('connection')->andReturnSelf();
$this->actingAs($user = User::factory()->create());
$message = Message::factory()->make(['sender_id' => $user->id]);
$conversation = Conversation::factory()->create();
$this->postJson("/api/message/{$conversation->id}", ['message' => $message->body]);
$this->assertDatabaseCount('messages', 1);
$redisSpy->shouldHaveReceived('publish')
->with('message', new MessageResource(Message::first()));
}
As you can see, I have added Redis::spy(); this is going to allow is to "spy" what is called from Redis. You can still mock methods, and we have to do so, because you use Redis::connect(); and then $redis->publish(...), so we will return the spy when connect is called, that is why we do shouldReceive('connection')->andReturnSelf().
At the end of the code, we check that $redis->publish was called with parameters 'message' and a resource with the desired message. Both must match for this assertion to pass, else you will see a mock error.

Find data before validate form request laravel

I want to update the data using the request form validation with a unique email role, everything works normally.
Assume I have 3 data from id 1-3 with url:
127.0.0.1:8000/api/user/update/3
Controller:
use App\Http\Requests\Simak\User\Update;
...
public function update(Update $request, $id)
{
try {
// UPDATE DATA
return resp(200, trans('general.message.200'), true);
} catch (\Exception $e) {
// Ambil error
return $e;
}
}
FormRequest "Update":
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->id,
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
but if the updated id is not found eg:
127.0.0.1:8000/api/user/update/4
The response gets The email has already been taken.
What is the solution so that the return of the data is not found instead of validation first?
The code looks like it should work fine, sharing a few things below that may help.
Solution 1: Check if $this->id contains the id you are updating for.
Solution 2: Try using the following changes, try to get the id from the URL segment.
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->segment(4),
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
Sharing one more thing that may help you.
Some person uses Request keyword at the end of the request name. The Update sounds generic and the same as the method name you are using the request for. You can use UpdateRequest for more code readability.
What I understand from your question is, you need a way to check if the record really exists or not in the form request. If that's the case create a custom rule that will check if the record exists or not and use that rule inside your request.
CheckRecordRule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class CheckRecordRule implements Rule
{
protected $recordId;
public function __construct($id)
{
$this->recordId = $id;
}
public function passes($attribute, $value)
{
// this will check and return true/false
return User::where('id', $this->recordId)->exists();
}
public function message()
{
return 'Record not found.';
}
}
Update form request
public function rules()
{
return [
'email' => 'required|email|unique:users,email,' . $this->id.'|'. new CheckRecordRule($this->id),
];
}
So when checking for duplicate it will also check if the record really exists or not and then redirect back with the proper message.

How to write TDD code for user profile update in Laravel

I want to create a TDD first before using my function in the app.
I have already created the update function, update works but before that i want a test case running. So i have created this test case.
/** #test */
public function updateUser()
{
$this->withExceptionHandling();
//creating a user
$user = factory(User::class)->create();
//passing updating values
$response = $this->put('users/update_profile', [
'name' => 'name123',
'phoneno' => 9842345562,
'address' => 'newwwww'
]);
$this->assertEquals('name123', User::first()->name);
$this->assertEquals(9842345562, User::first()->phoneno);
$this->assertEquals('newwwww', User::first()->address);
}
//update function
public function update(UpdateProfileRequest $request)
{
$user = auth()->user();
$user->update([
'name' => $request->name,
'phoneno' => $request->phoneno,
'address' => $request->address
]);
session()->flash('success', 'User Proifile Updated');
return redirect(route('users.view-profile'));
}
Failed asserting that two strings are equal.
--- Expected
+++ Actual
## ##
-'name123'
+'Tad Predovic'
Only getting this error.
You should not rely on User::first() as your first record may not the one you just created. Instead refresh the model you already have to get the updated values from the DB after your new values are set. You can use $user-refresh() before your assertions
/** #test */
public function updateUser() {
$this->withExceptionHandling();
//creating a user
$user = factory(User::class)->create();
//signing in as the new user
$this->actingAs($user);
//passing updating values
$response = $this->put('users/update_profile', [
'name' => 'name123',
'phoneno' => 9842345562,
'address' => 'newwwww'
]);
//Get new values
$user->refresh();
$this->assertEquals('name123', $user->name);
$this->assertEquals(9842345562, $user->phoneno);
$this->assertEquals('newwwww', $user->address);
}

LARAVEL & VUE: How can I get the API_TOKEN of the logged in user with an API request?

I have a SPA using VUE and LARAVEL 5.8
I have setup an API_TOKEN associated to the logged in user. Everything works fine right after the login. I get the API_TOKEN, I save it into a var and I send it together with the Axios request. In Laravel I have a middleware that is taking care of the token and comparing it with the one setup on the logged in user.
the problem though occur when session expires. Because I still can navigate the private pages and make API requests to save and delete content. This is possible I think because I still have the same API_TOKEN saved in the var and the middleware apparently doesn't get that the session is expired.
So I want to obtain the API_TOKEN every time I'm doing an Ajax, request so when the session expires, I won't get the token and therefore, I won't be able to complete the request.
This is my setup.
web.php is where I have the only php route that points to a singlePageController:
Auth::routes();
Route::get('/{any}', 'SinglePageController#index')->where('any', '.*');
Then in the singlePageController I return the view:
class SinglePageController extends Controller
{
public function index() {
return view('app', ['loggedUser' => auth()->user()]);
}
}
Then I have the api.php where I have the API routes. As you can see at the end I have the middleware to make it private. Just to make an example this is the one I use for updating the content:
Route::put('event/update/{slug}', 'EventController#update')->middleware('auth:api');
Then the related controller of that API route:
public function update(Request $request, $slug)
{
$event = Event::where('slug', $slug)->first();
$event->title = $request->input('title');
return new EventResource($event);
}
And in the end this is the Resource I use to define what and how the API data is going to be displayed:
public function toArray($request)
{
// return parent::toArray($request);
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'curator' => $this->curator,
'featured_image' => $this->featured_image,
'body' => $this->body,
'date' => $this->date
];
}
So this above is the flow I have. Then when I do an axios call to update the content, I'm doing something like:
axios({
method: 'PUT',
url: '/api/event/update/' + this.$route.params.slug + '?api_token=' + this.isLogged.apiToken,
data: dataToSave,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
})
.then((response) => {
this.getNotification('Success: The Event has been saved');
})
.catch((error) => {
this.getNotification('Error: Impossible saving the event');
console.log(error);
})
Do you know how to make it? or if there is a better way to accomplish that?
you and do like, your login method should like this.
public function login(Request $request)
{
if (Auth::attempt(['email' => $request['email'], 'password' => $request['password']])) {
$user = Auth::user();
$success = $user->createToken(config('app.name'))->accessToken;
return response()->json(["token" => $success, 'status' => 200]);
} else {
return response()->json(['message' => "Email or Password do not match"], 401);
}
}

Laravel login redirect doesn't work

I have made the login/tregistration form. Registration works well but login redirect doesn't work. I have the following function in my controller:
public function doLogin() {
$credentials = [
'email' => Input::get('email'),
'password' => Input::get('password')
];
if (Auth::attempt($credentials)) {
return Redirect::to('/');
} else {
dd('error');
}
}
and the routes.php
Route::resource('car', 'CarController');
Route::get('users', 'UserController#index');
Route::post('users/register', array('uses' => 'UserController#store'));
Route::post('users/signin', array('uses' => 'UserController#doLogin'));
Route::get('users/logout', array('uses' => 'UserController#doLogout'));
Route::get('/', 'CarController#index');
CarController
public function index() {
$cars = DB::select('select * from cars');
$result = DB::select('select c.*, i.sgs, i.tpl, i.kasko, i.inter_permis from cars as c left join insur_docs as i on i.car_id = c.id');
$date = Carbon::now();
$limit_date = Carbon::now()->addMonths(1);
return View::make('pages.index', array(
'cars' => $cars,
'result' => $result,
'date' => $date,
'limit_date' => $limit_date,
));
}
The problem is that it doesn't redirects to index page just refresh the page. If not correct credentials it shows "error" else if correct credentials it just refresh page and doesn't redirects. I f I replace redirect with success message it shows it. I have the same code localy and login with redirect is ok, but in google app engine (my project online) doesn't redirect.
The example you have used wouldn't actually redirect the user for two reasons.
The use of Redirect::route() excepts the parameter passed to be the name of a route, eg one defined like so
Route::get('/', ['as' => 'home', 'uses' => 'YourController#yourMethod']);
To redirect here you would use Redirect::route('home').
You aren't actually returning the redirect. Any response for a route, whether it be within a controller method or a closure, must be returned using the return keyword.
So to correct your code, it'd be like this:
public function doLogin() {
$credentials = [
'email' => Input::get('email'),
'password' => Input::get('password')
];
if (Auth::attempt($credentials)) {
return Redirect::to('/');
} else {
dd('error');
}
}
I moved the credentials to an array as it looks tidier and it makes it easier to read when displaying on this site, so you don't have to do that, but it may make things easier for you.

Resources