How to test Laravel Api (only) Controllers with PEST Php testing? - php

I've setup a backend only Laravel app from the boilerplate. I want to test the API controllers using Pest, however, I keep getting Unresolvable dependency resolving [Parameter #0 [ <required> $request ]] in class Illuminate\Http\Client\Request.
// UserControllerTest.php
use App\Models\User;
use function Pest\Laravel\{getJson, actingAs};
use Illuminate\Foundation\Testing\RefreshDatabase;
// Necessary to access Laravel testing helpers and database factory stuff
uses(
Tests\TestCase::class,
RefreshDatabase::class
);
// Auto set up a new authed user for each test
beforeEach(function() {
actingAs(User::factory()->create());
});
/**
* Test that the user can be retrieved and api succeeds
*/
it('Test get user succeeds', function () {
// Should get the user from the user controller
$response = getJson('api/user');
// Response should contain a user and be within the 200 range
$response->assertStatus(200);
});
Below is the user controller, just returns the logged in user from the request.
UserController.php
use Illuminate\Http\Client\Request;
public function index(Request $request)
{
// Show the current logged in user
$user = $request->user();
return new UserResource($user);
}
My return response is a 500 server error
Failed asserting that 200 is identical to 500.
The following exception occurred during the last request:
Illuminate\Contracts\Container\BindingResolutionException: Unresolvable dependency resolving [Parameter #0 [ <required> $request ]] in class Illuminate\Http\Client\Request in /home/sites/my-app/vendor/laravel/framework/src/Illuminate/Container/Container.php:1118
Stack trace:
#0 /home/sites/my-app/vendor/laravel/framework/src/Illuminate/Container/Container.php(1027): Illuminate\Container\Container->unresolvablePrimitive(Object(ReflectionParameter))
Has anyone used Pestphp to test api only laravel controllers? Is there something that needs to be mocked to test an api route with PEST?
Edit
This Testing Laravel API with Pest article is quite similar in structure, where they'd just use the $response = $this->getJson("/api/posts/{$post->id}"); to get the JSON response, however they don't have the same issue as me.
API Route
Route::group(['middleware' => ['auth:sanctum']], function () {
Route::apiResource('user', UserController::class);
}
// GET|HEAD api/user =>user.index › UserController#index
Addition Tests
Seems it's due to there being no \Illuminate\Http\Client\Request $request when calling `getJson('api/user');
Trying to get a specific user, where there's no $request param in the controller works.
// This works fine. No error like above.
it('Test get user succeeds', function () {
$user = User::factory()->create();
// Should get the user from the user controller
$response = getJson('api/user/' . $user->id);
// Response should contain a user and be within the 200 range
$response->assertStatus(200);
});
Versions
Laravel 9.19

Turns out the issue was I imported the incorrect Request $request class in my UserController
// Wrong Request Class
// use Illuminate\Http\Client\Request;
// Proper Request Class
use Illuminate\Http\Request;

Related

Where does "auth" come from

I'm currently learning Lumen, I'm running a fresh install of Laravel 6.x .
I'm using RESTClient to stimulate my Lumen API/send http-Requests.
I want to set up some basic authentication with JWT now, and I already accomplished this.
I get a valid JWT and when I pass it through the following code, the firebase middleware (5.x) accepts the token, if valid, and rejects it if invalid.
The code doing the validation resides inside my AuthServiceProvider.php:
<?php
namespace App\Providers;
use App\User;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
//new Code
class AuthServiceProvider extends ServiceProvider
{
public function register()
{
//
}
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request) {
$key = 'pawifjopawiejfpoaiwejfpoji';
$jwt = preg_replace('/^Bearer (.*)/', '$1', $request->header('Authorization'));
$decoded = JWT::decode($jwt, $key, ['HS256']);
//return User::where('email', $decoded->email)->first();
return "Hello From Auth!";
});
}
}
The router calling the middleware and routing its result to the controller resides in web.php and looks like this:
$router->get('myStorage', ['middleware' => 'auth', 'uses' => 'AuthController#myStorage']);
Now, my problem is this:
I've looked into all the depenencies inside providers/AuthServiceProvider.php, ServiceProvider.php insinde vendor/Illuminate/Support, Authenticate.php inside app/http/middleware and also bootstrap/app.php where the registering takes place.
The "auth" key/denominator, as it appears multiple times inside the sourcecode and also references the authentication middleware during routing, remains a mystery to me.
I don't know what object it points to.
I've tested changing it to "auth2" in all the places I found it (see above), but then I get this exception thrown by Lumen:
(2/2) BindingResolutionException
Target class [auth2] does not exist.
So somewhere, this class must have been defined.
Inside Authenticate.php, there is this line in the head of the code
use Illuminate\Contracts\Auth\Factory as Auth;
And I've manipulated it, alongside its occurrences in the whole code of authenticate.php .
But I always get the same error.
I want to know where this "auth" is coming from, because I also want to employ a middleware for AUTHORIZATION, following the AUTHENTICATION.
The routes would then look kinda like this:
$router->get('myStorage', ['middleware' => ['authenticate', 'authorize'], 'uses' => 'AuthController#myStorage']);
To do this, I think I need a better understanding of how and where this (third-party) middleware/its components are defined. Otherwise I will probably run into issues when registering this stuff for example.

access Request object in route group - Slim Framework

I´m using Slim Framework and I need to access the Request object in a group so I can create objects and use them in the routes
$app->group('/my-group', function (App $app) {
$id = $app->request->getAttribute('id')); // this doesn´t work
$user = some_method_to_find_user($id)
$app->get('/route-1', function () use ($user) {
var_dump($user);
}
}
how to access the Request object?
I also tried with
$app->group('/api', function (App $app, Request $request) {
$id = $request->getAttribute('id')); // this doesn´t work
but is giving me this error:
Uncaught ArgumentCountError: Too few arguments to function Closure::{closure}(),
groups are designed only to create Router (and by name - route groups)
you should use and access request only in Middlewares and Controllers (ie. clousures used as route)
during Group call the request might not be determined yet
application is building entire router (groups, each route,..)
and then by requested URI router will fill and pass Request to your middlewares and to route stack
use Slim Documentation, it is full of valid examples:
http://www.slimframework.com/docs/v3/objects/router.html#how-to-create-routes
$app->get('/books/{id}', function ($request, $response, $args) {
// Show book identified by $args['id']
});

Laravel - adding VerifyCsrfToken to controller getting ajax request

I am creating a controller that receive an AJAX request and from Laravel documentation, i can send header with X-Csrf token
https://laravel.com/docs/5.5/csrf#csrf-x-csrf-token
On my Controller i have something like this :
public function checkPromotion(Request $request)
{
try {
$this->middleware('VerifyCsrfToken');
}
catch (TokenMismatchException $e){
return response()->json(['error' => 'Error Token Provided']);
}
}
}
When i tried and sent a blank post request to this controller , the respond was blank .
There are three issues here:
If you are going to add middleware in a controller, you must do so in the constructor.
Out of the box, you cannot handle middleware exceptions in a controller action. You'll need to handle the exception in your \App\Exceptions\Handler class. The handle method of that class will receive the TokenMismatchException when token verification fails.
The string 'VerifyCsrfToken' is not a valid way to reference your middleware.
Regarding #3, the middleware method takes the name of a middleware group, or the FQCN of a particular middleware.
The following should work:
$this->middleware(\App\Http\Middleware\VerifyCsrfToken::class)
(I'm assuming that you are using the default App namespace)
If you get a "Session store not set on request" exception, it's because cannot use CSRF middleware without the StartSession middleware.
Most likely what you really want is the web middleware:
$this->middleware('web')
This will include the CSRF middleware, the session start middleware, and a few others (see your http kernel for details).
If needed you can exclude routes from CSRF verification by using the $except array in your VerifyCsrfToken class
The middleware method on Controller is registering a middleware in an array on the controller. That middleware is not ran at that point.
When this method is called in a constructor the router/route collection will have access to the getMiddleware method after the Controller has been resolved to be able to build the middleware stack needed for the current route/action.
You may want to be dealing with this exception in your exception handler, App\Exceptions\Handler.
Laravel 5.5 Docs - Errors - The Exception Handler - Render Method
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Session\TokenMismatchException) {
return response()->json(['error' => 'Error Token Provided']);
}
return parent::render($request, $exception);
}
protected function tokensMatch($request)
{
// If request is an ajax request, then check to see if token matches token provider in
// the header. This way, we can use CSRF protection in ajax requests also.
$token = $request->ajax() ? $request->header('X-CSRF-Token') : $request->input('_token');
return $request->session()->token() == $token;
}
Add this function inside VerifyCsrfToken.php

Laravel 5 Basic Auth custom error

In Laravel 5, if basic auth fails for a user then the default message that is returned is an "Invalid Credentials" error string. I am trying to return a custom JSON error when this situation occurs.
I can edit the returned response in vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
however I have not seen where you can change the behavior of this message outside of the vendor directory. Is there a way?
Looks like there were some ways to do this through Laravel 4: Laravel 4 Basic Auth custom error
Figured it out, looks like I had to create custom middleware to handle this.
Note that this solution didn't work when calling my API from my browser, only when calling it from a tool like Postman. For some reason when calling it from my browser I always got the error before seeing the basic auth prompt.
In my controller I changed the middleware to my newly created one:
$this->middleware('custom');
In Kernel I added the location for it:
protected $routeMiddleware = [
'auth.basic.once' => \App\Http\Middleware\Custom::class,
]
Then I created the middleware. I used Stateless Basic Auth since I'm creating an API:
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
use Illuminate\Http\Request as HttpRequest;
use App\Entities\CustomErrorResponse
class Custom
{
public function __construct(CustomErrorResponse $customErrorResponse) {
$this->customErrorResponse = $customErrorResponse
}
public function handle($request, Closure $next)
{
$response = Auth::onceBasic();
if (!$response) {
return $next($request);
}
return $this->customErrorResponse->send();
}
}

Redirect to route not working in Laravel 5

I have an app where the user submits a form which performs a SOAP exchange to get some data from a Web API. If there are too many requests in a certain time, the Throttle server denies access. I have made a custom error view for this called throttle.blade.php which is saved under resources\views\pages. In routes.php I have named the route as:
Route::get('throttle', 'PagesController#throttleError');
In PagesController.php I have added the relevant function as:
public function throttleError() {
return view('pages.throttle');
}
Here is the SoapWrapper class I have created to perform the SOAP exchanges:
<?php namespace App\Models;
use SoapClient;
use Illuminate\Http\RedirectResponse;
use Redirect;
class SoapWrapper {
public function soapExchange() {
try {
// set WSDL for authentication
$auth_url = "http://search.webofknowledge.com/esti/wokmws/ws/WOKMWSAuthenticate?wsdl";
// set WSDL for search
$search_url = "http://search.webofknowledge.com/esti/wokmws/ws/WokSearch?wsdl";
// create SOAP Client for authentication
$auth_client = #new SoapClient($auth_url);
// create SOAP Client for search
$search_client = #new SoapClient($search_url);
// run 'authenticate' method and store as variable
$auth_response = $auth_client->authenticate();
// add SID (SessionID) returned from authenticate() to cookie of search client
$search_client->__setCookie('SID', $auth_response->return);
} catch (\SoapFault $e) {
// if it fails due to throttle error, route to relevant view
return Redirect::route('throttle');
}
}
}
Everything works as it should until I reach the maximum number of requests allowed by the Throttle server, at which point it should display my custom view, but it displays the error:
InvalidArgumentException in UrlGenerator.php line 273:
Route [throttle] not defined.
I cannot figure out why it is saying that the Route is not defined.
You did not define a name for your route, only a path. You can define your route like this:
Route::get('throttle', ['as' => 'throttle', 'uses' => 'PagesController#throttleError']);
The first part of the method is the path of the route in your case you defined it like /throttle. As a second argument you can pass array with options in which you can specify the unique name of the route (as) and the callback (in this case the controller).
You can read more about routes in the documentation.

Categories