How run delete method in TDD tests? - laravel-5

In laravel 5.8 app I make TDD tests for admin CRUD operations for table , make post, put requests, but I failed to make delete :
public function testVoteCategoriesCrud()
{
$this->withoutExceptionHandling();
$this->withoutMiddleware();
$newVoteCategoryRow = factory('App\VoteCategory')->make(); // Create new VoteCategory Data Row
$newVoteCategoryRow->name .= ' created on ' . strftime("%Y-%m-%d %H:%M:%S:%U") ;
$new_vote_category_row_name = $newVoteCategoryRow->name;
$response = $this->actingAs($loggedUser)->post('/admin/vote-categories', $newVoteCategoryRow->toArray());
$this->assertCount( $original_vote_categories_count+1, VoteCategory::all() ); // to use HTTP_RESPONSE_OK
$response->assertRedirect('/admin/vote-categories'); // after successful post request redirect to '/admin/vote-categories'
...
$response = $this->actingAs($loggedUser)->delete( '/admin/vote-categories', ['id'=>$checkCreatedVoteCategory->id] );
$this->assertCount( $original_vote_categories_count, VoteCategory::all() ); // to use HTTP_RESPONSE_OK
$response->assertRedirect('/admin/vote-categories'); // after successful put request redirect to '/admin/vote-categories'
But I got error
Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: The DELETE method is not supported for this route. Supported methods: GET, HEAD, POST.
on delete method above.
How correctly delete row in tests?

I found a decision to add delete method in routes/web.php:
Route::resource('vote-categories', 'Admin\VoteCategoriesController', ['except' => []])->middleware('WorkTextString');
Route::delete('vote-categories', 'Admin\VoteCategoriesController#destroy');
and that works for me

Related

Can i run php unit tests using Laravel Policy in route middleware?

I am using Laravel policies to control authorisation on my api routes. I want to only allow the current user to update their own post. This works fine when i manually run it through the application, but the unit tests fail. The unit tests, redirect to the login screen.
route:
Route::post('/posts/{post:reference}/editDetails', [PostDetailsApiController::class, 'update'])
->middleware('can:update,post');
policy:
public function update(User $user, Post $post)
{
return $post->user_id === $user->id;
}
unit test:
$this->user = User::factory()->create();
$post = Post::factory()->create(['user_id' => $this->user->id]);
Passport::actingAs($this->user);
$response = $this->call('POST', 'http://test/api/posts/' . $post->reference . '/editDetails');
$response->assertStatus(200);
but this test fails saying 'Failed asserting that 200 is identical to 302'. If i add followingRedirects() and actingAsClient then it passes. If i dd in the controller, it doesnt get fired, so i'm pretty sure the controller isnt getting hit? if i remove the middleware, it runs fine. any advice welcomed. thanks
This has nothing to do with the policy as the status code 302 is a redirect. The problem lies within this snippet.
$this->call('POST', 'http://test/api/posts/' . $post->reference . '/editDetails');
Normally you would call it with a relative URL.
$this->call('POST', 'api/posts/' . $post->reference . '/editDetails');
A better approach is to use the route helper for named routes. I have a example project, where i used this approach.
$this->call('POST', route('posts.edit-details', ['reference' => $post->reference]));
Remember to add a name to your route.
Route::post('/posts/{post:reference}/editDetails', [PostDetailsApiController::class, 'update'])
->middleware('can:update,post')
->name('posts.edit-details');

consuming webservice laravel

I have two projects in laravel, one only with my vision (AppView) and another with the web service (AppWs), in my web service I have the following route
Route project AppWs
Route::group(['prefix' => 'api'],function(){
Route::group(['prefix' => 'user'], function(){
Route::group(['prefix' => 'tipoprojeto'], function(){
Route::get('','Painel\TipoProjetoController#All');
});
});
});
when I access http://localhost/WebServiceApp/public/api/user/tipoprojeto/
it returns me an array with all the data, until then all right.
in my other project, I have a TypeProjectController controller and I have my index () method (AppView), so how can I retrieve the webservice data to load here?
EDIT
AppWs responsible for manipulating the data
public function All(){
return $this->ModelTipoProjeto->paginate(5);
}
AppView responsible for displaying data
Route::resource('/Painel/TipoProjeto', 'Painel\TipoProjetoController');
public function index()
{
$getData = `http://localhost/WebServiceApp/public/api/user/tipoprojeto/` // <~~
return view('Painel.TipoProjeto.index');
}
Retrieve the data that the AppWebservice link returns
First of all in order to consume an external service you have to perfrom a http request towards the endpoint where you intend to get the data form.
Your endpoing: http://localhost/WebServiceApp/public/api/user/tipoprojeto/
Install guzzle which is a php curl wrapper to perform http calls.
In your root dir open command line and inject guzzle in your project by firing :
composer require guzzlehttp/guzzle
Make sure you import guzzle at the top of the controller by adding
use GuzzleHttp\Client;
Then go to your index method and do the following:
public function index(){
// Create a client with a base URI
$client = new GuzzleHttp\Client(['base_uri' => 'http://localhost/WebServiceApp/public/api/user/tipoprojeto/']);
// Send a request to http://localhost/WebServiceApp/public/api/user/tipoprojeto/
$response = $client->request('GET', 'test');
// $response contains the data you are trying to get, you can do whatever u want with that data now. However to get the content add the line
$contents = $response->getBody()->getContents();
dd($contents);
}
$contents contains the data now you can do whatever you want.

Laravel 5.3 dynamic routing to multiple controllers

I'm using Laravel 5.3. I have a bunch of urls that I'd like to handle with a single route, to multiple controllers.
e.g.
GET /admin/foo => FooController#index
GET /admin/foo/edit/1 => FooController#edit($id)
GET /admin/bar => BarController#index
GET /admin/bar/edit/1 => BarController#item($id)
GET /admin/baz => BazController#index
GET /admin/baz/edit/1 => BazController#item($id)
etc.
I want to be able to detect if the controller exists, and if not throw a 404 or route to a default controller (which may throw a 404).
Below is what I've got so far, but I'm not sure what I'm doing. Shouldn't I be instantiating the controller using the service container? I don't think I should be hardcoding namespaces like this. And my handling of the id parameter is sketchy. Perhaps I should have two routes for these two patterns or something?
Route::get('/admin/{entityType}/{action?}/{id?}', function ($entityType, $action = 'index', $id = null) {
$controllerClass = 'App\Http\Controllers\\' . ucfirst($entityType) . 'Controller';
$controller = new $controllerClass;
$route = app(\Illuminate\Routing\Route::class);
$container = app(\Illuminate\Container\Container::class);
return (new Illuminate\Routing\ControllerDispatcher($container))->dispatch($route, $controller, $action);
abort(404);
});
I'd recommend you to define a route for every controller explicitly. This is the best way to build a maintainable app.
Also, if using one route and one method is an option (with right architecure it is) use one route:
Route::get('/admin/{entityType}/{action?}/{id?}', 'Controller#method');
And one entry point:
public function method($entity, $action = null, $id = null)
{
// Handle request here.
https://laravel.com/docs/5.3/routing#parameters-optional-parameters

Laravel 5 - Route not available in notFoundHttpException

Currently I'm experiencing an error I find hard to fix. The error concerns the \Request::route() returning NULL when the notFoundHttpException is fired and my custom error view is rendered.
The problem is that my application needs the current route in order to do a variety of things. I think it is "normal" that ..::route() returns NULL since the route does not exists.
This is where I show my custom error view:
// App\Exceptions\Handler
return response()->view('layouts.default', [
'main_content' => view('errors.'.$e->getStatusCode())
], $e->getStatusCode());
When I call \Request::route() inside my view it returns NULL.
Anyone has any idea how to solve this in a clean way?
Edit
The application I am currently working on is a multi domain system. The route is needed to determine what domain is currently requested.
All routes are wrapped inside one group:
Route::group(['domain' => {domain}.{tld}], function()
{
// Application routes...
}
To determine the domain:
$domain = \Request::route()->domain;
$tld = \Request::route()->tld;
// Retrieve from database...
Yes there are work arounds like:
// Url requested: mydomain.com/notexistingpage
$url_parameters = #explode(".", $_SERVER['HTTP_HOST']);
$domain = $url_parameters[0];
$tld = $url_parameters[1];
// Retrieve from database...
However I would like to keep using the router to serve the parameters. I'll mark this question as answered.
Try this
\Route::getFacadeRoot()->current()->uri();
or
\Route::getCurrentRoute();
A work around could be:
// Url requested: mydomain.com/notexistingpage
$url_parameters = #explode(".", $_SERVER['HTTP_HOST']);
$domain = $url_parameters[0];
$tld = $url_parameters[1];
// Retrieve from database...

Laravel 5: Calling routes internally

Is there a way, in Laravel 5, to call routes internally/programmatically from within the application? I've found a lot of tutorials for Laravel 4, but I cannot find the information for version 5.
Using laravel 5.5, this method worked for me:
$req = Request::create('/my/url', 'POST', $params);
$res = app()->handle($req);
$responseBody = $res->getContent();
// or if you want the response to be json format
// $responseBody = json_decode($res->getContent(), true);
Source:
https://laracasts.com/discuss/channels/laravel/route-dispatch
*note: maybe you will have issue if the route you're trying to access
has authentication middleware and you're not providing the right credentials.
to avoid this, be sure to set the correct headers required so that the request is processed normally (eg Authorisation bearer ...).
UPDATE: i've tried this method with laravel 8 and it works but if you're using PHP version 8.0 you might need to call opcache_reset(); before this line $req = Request::create('/my/url', 'POST', $params); to avoid an error.
see guzzlehttp/guzzle dosn't work after update php to php 8 for more info
You may try something like this:
// GET Request
$request = Request::create('/some/url/1', 'GET');
$response = Route::dispatch($request);
// POST Request
$request = Request::create('/some/url/1', 'POST', Request::all());
$response = Route::dispatch($request);
You can actually call the controller that associates to that route instead of 'calling' the route internally.
For example:
Routes.php
Route::get('/getUser', 'UserController#getUser');
UserController.php
class UserController extends Controller {
public function getUser($id){
return \App\User::find($id);
};
}
Instead of calling /getUser route, you can actually call UserController#getUser instead.
$ctrl = new \App\Http\Controllers\UserController();
$ctrl->getUser(1);
This is the same as calling the route internally if that what you mean. Hope that helps
// this code based on laravel 5.8
// I tried to solve this using guzzle first . but i found guzzle cant help me while I
//am using same port. so below is the answer
// you may pass your params and other authentication related data while calling the
//end point
public function profile(){
// '/api/user/1' is my api end please put your one
//
$req = Request::create('/api/user/1', 'GET',[ // you may pass this without this array
'HTTP_Accept' => 'application/json',
'Content-type' => 'application/json'
]);
$res = app()->handle($req);
$responseBody = json_decode($res->getContent()); // convert to json object using
json_decode and used getcontent() for getting content from response
return response()->json(['msg' =>$responseBody ], 200); // return json data with
//status code 200
}
None of these answers worked for me: they would either not accept query parameters, or could not use the existing app() instance (needed for config & .env vars).
I want to call routes internally because I'm writing console commands to interface with my app's API.
Here's what I did that works well for me:
<?php // We're using Laravel 5.3 here.
namespace App\Console;
use App\MyModel;
use App\MyOtherModel;
use App\Http\Controllers\MyController;
use Illuminate\Console\Command;
class MyCommand extends Command
{
protected $signature = 'mycommand
{variable1} : First variable
{variable2} : Another variable';
public function handle()
{
// Set any required headers. I'm spoofing an AJAX request:
request()->headers->set('X-Requested-With', 'XMLHttpRequest');
// Set your query data for the route:
request()->merge([
'variable1' => $this->argument('variable1'),
'variable2' => $this->argument('variable2'),
]);
// Instantiate your controller and its dependencies:
$response = (new MyController)->put(new MyModel, new MyOtherModel);
// Do whatever you want with the response:
var_dump($response->getStatusCode()); // 200, 404, etc.
var_dump($response->getContent()); // Entire response body
// See what other fun stuff you can do!:
var_dump(get_class_methods($response));
}
}
Your Controller/Route will work exactly as if you had called it using curl. Have fun!

Resources