Exception handling with phpunit and Silex - php

I am using a route in Silex to delete an object from the database. If an object does not exist, a 404 error should be thrown. This works fine in the browser, and the response is received accordingly.
This is my source:
$app->delete("/{entity}/{id}", function(\Silex\Application $app, HttpFoundation\Request $request, $entity, $id) {
// some prep code is here
$deleteObject = $this->em->getRepository($entityClass)->find($id);
if (empty($deleteObject))
$app->abort(404, "$ucEntity with ID $id not found");
// other code comes here...
}
This is my test case:
// deleting the same object again should not work
$client->request("DELETE", "/ccrud/channel/$id");
$this->assertTrue($response->getStatusCode() == 404);
Now phpunit fails with the following error:
1) CrudEntityTest::testDelete
Symfony\Component\HttpKernel\Exception\HttpException: Channel with ID 93 not found
I can see from the message that the 404 was thrown, but I cannot test the response object as planned. I know that in theory I could assert for the exception itself, but that is not what I want to do, I want to get the response (as a browser would do as well) and test for the status code itself.
Anybody any ideas how to reach that or if there is a better way to test this?
Thanks,
Daniel

This is how it is being done in the tests of Silex itself (see here):
public function testErrorHandlerNotFoundNoDebug()
{
$app = new Application();
$app['debug'] = false;
$request = Request::create('/foo');
$response = $app->handle($request);
$this->assertContains('<title>Sorry, the page you are looking for could not be found.</title>', $response->getContent());
$this->assertEquals(404, $response->getStatusCode());
}

Related

Guzzle how to handle RequestException when host is offline

I am using Guzzle inside the laravel framework but when the host is offline I get a exception instead of a statuscode which you can get with getStatusCode(). I have now made a try catch around the request, but outside this method I have a method which checks on the statuscode. My question is how can I return in the catch the correct response so I could call outside this method getStatusCode().
My code to make the request looks like this:
public function makeRequest($method, $requestUrl, $queryParams = [])
{
try{
$client = new Client(['http_errors' => false]);
return $client->request($method, $requestUrl, [
'query' => $queryParams
]);
}catch(RequestException $exception){
LOG::info($exception->getMessage());
return $exception->getResponse();
}
}
You idea (always return Response object from the method) doesn't work by design. I mean that you see it already, there is no Response object is some cases (when a connection to the host cannot be established, for example, so HTTP flow doesn't even start in this case, that why you don't have any HTTP status code).
IMO the best way to make your code aware of the exception. Don't try to handle it inside your makeRequest(), just let it flow further, to the point where you actually can handle it.

How can multiple method calls be chained on slim's response object?

I want to chain multiple method calls on slims's $response object, but if i do i get an error ( status 500) and nothing happens.
This might aswell be a lack of basic PHP knowledge, i am not very experienced in PHP and it is my first time working with slim, or any serverside / API framework for that matter.
I have tried flipping arround the order of the calls, and doing them in different lines but to no awail. The long term goel of this, to build an API for an update application. So i will have to handle get requests with multiple parameters, evaluate them and depending on the results, do and return deffirent responses.
// this one fails, i set the status to 900 on purpose just to see what happens
$app->get('/', function (Request $request, Response $response, array $args) {
$response->getBody()->write("Slim main page")->withStatus(900);
return $response;
});
The first example does give me an 500 error on the network tab. This would suggest some type of syntax error i guess? If i alter this route a little bit to look like this :
# this one works fine, except the status code setting gets ignored, but why?
$app->get('/', function (Request $request, Response $response, array $args) {
$response->write("Slim main page")->withStatus(900);
return $response;
});
things alsmost work out, but the status code is not set for some reason.
I would expect the first one, to return the string "slim main page" with the status code 900. Even if i use a non made up status code, this setting gets ignored.
The second code block ist just an alteration for testing purposes.
I am pretty sure this a newby thing but i am really lost here, so any advice, or some fool proof articles / docs besides the slim docs are appreciated.
The write method returns the number of bytes written to the stream (and not the new response object). Try this:
$app->get('/', function (Request $request, Response $response, array $args = []) {
$response->getBody()->write('Slim main page');
$response = $response->withStatus(200);
return $response;
});
Notice 1: Enable the error details on dev: 'displayErrorDetails' => true
Notice 2: The HTTP code 900 is an invalid status code for Slim and will throw the following execption.
Type: InvalidArgumentException
Message: Invalid HTTP status code
File: vendor/slim/slim/Slim/Http/Response.php
Line: 228

How to return/terminate symfony2 response in function other than the requested action?

I'm building a RESTful API using Symfony2, FOSRestBundle and an OAuth 2 server library.
For any given request, there are a number of possible responses and status codes that I can return to the client.
Take for example the process of getting a user:
<?php
class UserController extends CustomBaseController {
/**
* Get a user
* #Get("/users", name="users_get")
*/
public function getAction(Request $request) {
// Ensure a valid access token is present
$this->validAccessTokenOrExit();
$user = $this->getUser();
return $this->handleView($this->view($user, 200));
}
}
Ideally I would like to have the validAccessTokenOrExit() function terminate the request and return the 401 status code with an appropriate message. This means I can handle authentication in this function and reuse it in several actions across the API.
However I can't seem to find a way of terminating the response from another function, and I always have to return a Response from the action called.
Are there any clean solutions to accomplish this or something similar?
If you throw an exception that has the interface Symfony\Component\HttpKernel\Exception\HttpExceptionInterface (Symfony\Component\HttpKernel\Exception\HttpException for example) with the status code set (first parameter as 401 for HttpException) it will be handled by the HttpKernel in the way that you are expecting.
For example..
throw new HttpException(401, 'Something went wrong');
.. will be turned into a response with 401 as the status code.
You can use $this->createAccessDeniedException('message') within a controller.
This will create AccessDeniedException, terminate the current request and return a 403 with your message.
I did faced this situation and for time being I call the terminating function with return. Return false from function when valid.
if($r=$this->validAccessTokenOrExit()){
return $r;
}
Alternatively I use following two methods for redirecting or rendering a view form another function and terminating flow.
Redirection:
header('location:'. $this->generateUrl('deals_product_view', array('id' => 1)));
exit;
Rendering a view:
$response = new Response();
$response->setContent($this->renderView($view_file, $params ));
$response->send();
exit;

Laravel resource controller testing gives NotFoundHttpException the second time

I am encountering a very strange thing while doing testing with Laravel 4. It looks like a bug but there's probably a logical explanation.
I have replicated the "bug" in a clean Laravel install and here's my code:
My resource controller /app/controllers/TestController.php:
(Created with php artisan controller:make TestController)
class TestController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
return Response::json(array());
}
// The end of the file is unchanged
In my app/routes.php:
Route::get('/', function()
{
return View::make('hello');
});
// Note the composed part of the URL.
// My problem isn't present if I just use `myapi` or `path` as a url
Route::resource('myapi/path', 'TestController');
Added in /app/test/ExampleTest.php:
public function testTest()
{
$res = $this->call('GET', 'myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', 'myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
Now I run phpunit and here's what I get:
There was 1 error:
1) ExampleTest::testTest
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/laraveltest/bootstrap/compiled.php:5531
/home/me/Web/laraveltest/bootstrap/compiled.php:4848
/home/me/Web/laraveltest/bootstrap/compiled.php:4836
/home/me/Web/laraveltest/bootstrap/compiled.php:4828
/home/me/Web/laraveltest/bootstrap/compiled.php:721
/home/me/Web/laraveltest/bootstrap/compiled.php:702
/home/me/Web/laraveltest/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/laraveltest/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:332
/home/me/Web/laraveltest/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/laraveltest/app/tests/ExampleTest.php:25
In my other project I get a slightly different backtrace, but I have the impression that's the same problem: (but I have no idea of why the other is compiled and this one not)
2) UnitModelTest::testOther
Symfony\Component\HttpKernel\Exception\NotFoundHttpException:
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/RouteCollection.php:148
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1049
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1017
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Routing/Router.php:996
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:775
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:745
/home/me/Web/my-project/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/home/me/Web/my-project/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:327
/home/me/Web/my-project/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/home/me/Web/my-project/app/tests/UnitModelTest.php:32
In both case the line given in the trace for the test file corresponds to the second call of the test.
As I noted in the comments of the routes.php file, if I use a simple url with no slash, the test passes without problem.
I have no problem when I use the api from the browser.
I found many topics related to the NotFoundHttpException on StackOverflow, but none looks like mine. This one is specifically present when testing and only trigger an error at the second call.
So what am I doing wrong here ? Or is it really a bug ?
The problem is that calls made with the same client will use the provided URI relatively. That means what you actually call is:
myapi/path
myapi/myapi/path
You can fix this if you add a preface the urls with a / to make them absolute to the root of the application.
public function testTest()
{
$res = $this->call('GET', '/myapi/path');
// Until here everything works right
$this->assertEquals(json_decode($res->getContent()), array());
// Now I call the same URL a second time
$res = $this->call('GET', '/myapi/path');
$this->assertEquals(json_decode($res->getContent()), array());
}
If you experience other issues like that it often helps to call
$this->refreshApplication();
(This would also create a new client and therefore solve this issue as well)
In my case this error happened because i change public directory name to public_html my solution was put this in the \App\Providers\AppServiceProvider register method.
public function register()
{
// ...
$this->app->bind('path.public', function() {
return base_path('public_html');
});
}

Slim framework halt error codes not working

I have just built a restful API with Slim Framework. For error conditions I simply respond with appropriate error codes for each error case and called with $app->halt, for example:
$app->halt(403, "Unauthorized");
But when I curled my API with -v and when I viewed headers in Firefox with HTTPFox I am always seeing error code 500. Anyone else notice this? Is there something I'm missing?
I ran into this same issue myself recently because I had forgotten to instantiate the $app variable within my function.
If you are not explicitly stating for your function to use($app), try adding the following line before $app-halt(403, 'Unauthorized') in order to see the desired error code:
$app = Slim::getInstance();
It is not allowed to call halt() method outside of the route callback.
You should use like this;
$app->get('/method/', function () {
//logical controls
//do something
//or
$app->halt();
});
There is a difference between halt() and setStatus().
With halt(), you will stop the current script execution and render a response according to the HTTP status code and message you choose to send. You can do it anywhere in your app with this code :
$app = \Slim\Slim::getInstance(); //if you don't have access to $app
$statusCode = 403;
$body = 'Unauthorized';
$app->halt($statusCode, $body);
//App will stop immediately
With setStatus() or $this->response->status(); you will only change the HTTP status code you are sending but your app will continue to execute like normally and won't stop. Its only changing the header that Slim will send to your client at then end of the route execution.
$app = \Slim\Slim::getInstance(); //if you don't have access to $app
$statusCode = 403;
$app->response->setStatus(400);
//App will continue normally

Categories