Setter in Guzzle 5 is not allowed in Guzzle6 - php

I'm using Guzzle to make tests on the API i'm working on in Symfony2.
I've been watching the knpUniversity series on Rest, and i have trouble updating the part where i should be able to use the app_test.php instead of app.php.
In the series, they use Guzzle v5, but in v6 there are major changes with PSR7 use
In the tutorial, to permit the use of another uri ending with other than app.php, they use :
use GuzzleHttp\Event\BeforeEvent;
...
class ApiTestCase extends KernelTestCase
{
...
public static function setUpBeforeClass()
{
$baseUrl = getenv('TEST_BASE_URL');
self::$staticClient = new Client([
'base_url' => $baseUrl,
'defaults' => [
'exceptions' => false
]
]);
...
// guaranteeing that /app_test.php is prefixed to all URLs
self::$staticClient->getEmitter()
->on('before', function(BeforeEvent $event) {
$path = $event->getRequest()->getPath();
if (strpos($path, '/api') === 0) {
$event->getRequest()->setPath('/app_test.php'.$path);
}
});
self::bootKernel();
}
But with Guzzle6, there is no setter for the request :
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ApiTestCase extends KernelTestCase
{
private static $staticClient;
protected $client;
public static function setUpBeforeClass()
{
//defined in phpunit.xml
$baseUri = getenv('TEST_BASE_URI');
//Create a handler stack that has all of the default middlewares attached
$handler = HandlerStack::create();
// guaranteeing that /app_test.php is prefixed to all URLs
$handler->push(Middleware::mapRequest(function (RequestInterface $request) {
$path = $request->getUri()->getPath();
if (strpos($path, '/api') === 0) {
$request->getUri()->setPath('/app_test.php'.$path); // <-- this is not allowed
}
// Notice that we have to return a request object
return $request->withHeader('X-Foo', 'Bar');
}));
// Inject the handler into the client
self::$staticClient = new Client([
'base_uri' => $baseUri.'/app_test.php',
'handler' => $handler,
'defaults' => [
'http_errors' => false
]
]);
//Allows to use service container
self::bootKernel();
}
So how can i change the uri, to use the app_test.php instead of app.php ?
Thanks
===========================
EDIT :
Actually, they give a solution in one of the post of the comment
$handler->push(Middleware::mapRequest(function(RequestInterface $request) {
$path = $request->getUri()->getPath();
if (strpos($path, '/app_test.php') !== 0) {
$path = '/app_test.php' . $path;
}
$uri = $request->getUri()->withPath($path);
return $request->withUri($uri);
}));
Thanks #Cerad

Related

Testing API call in laravel controller/view

I'm testing an API call that I have working in Postman and other interfaces built over the API, but I'm trying to actually get my return values within my laravel site.
Should be simple enough, I made a very bare service file that uses guzzle to get to the API url, and I have a function in there for logging in using a test email, password and client ID (all of which work in the api interface such as Postman)
I'm calling the class and function inside my authController and then returning the function call in the view, then dumping that inside the view.
However, currently I'm getting null on my page dump.
Is there something I'm missing here, possibly in my service file. I don't think there should be anything passed here, but maybe I'm overlooking something more obvious.
Authorize.php
<?php
namespace App\Services\restAPI;
use Illuminate\Support\Facades\Auth;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use Session;
use Exception;
class AuthService
{
protected $baseurl;
protected $guzzleClient;
public function __construct() {
$this->baseurl = config('http://ip.address');
$this->baseurl = rtrim($this->baseurl, '/') . '/'; // ensure trailing slash
$this->guzzleClient = new \GuzzleHttp\Client(
['verify' => config('app.ca_pem_file'),
'headers' => [
'Event-ID' => ACCESS_EVENT_ID
],
'base_uri' => $this->baseurl
]
);
}
public function loginGetToken(){
$password = "password";
$email = "test#thisAPI.com";
$client_id = "0";
$payload = (object)[
'password'=>(string)$password,
'email'=>(string)$email
];
$retval = $this->post('login/?client_id='.$client_id,$payload);
return $retval;
}
private function post($endpoint,$payload){
$result = $this->guzzleClient->request('POST', $endpoint, ['json'=>$payload]);
$body = $result->getBody();
return json_decode($body);
}
}
AuthController.php
use Redirect;
use Illuminate\Http\Request;
use App\Services\restAPI\AuthService;
class AuthController extends Controller
{
public function login(Request $request)
{
$authService = new AuthService();
$login = $authService->loginGetToken();
return redirect(route('auth.login'))
->with('login', $login)
}
login.blade.php
<?php
dd($login);
?>
Since you are redirecting, the data is not directly available in the view as a variable. It should be in the session though, so you can try this in your view:
dd(session('login'));
https://laravel.com/docs/5.7/redirects#redirecting-with-flashed-session-data

Mockery, App Instance not working on some laravel tests

Basically, my question is; Why does the $this->app->instance() Call work on one instance of the mocked object, but the other doesn't...
In the example below, the getGroupSingleSignOnLink function actually gets called, the other is mocked and the test passes...
TEST
namespace Tests\Feature;
use App\Models\Group;
use App\Models\User;
use Tests\TestCase;
use App\Clients\SingleSignOnApi;
use Mockery;
class SingleSignOnTest extends TestCase
{
private $validUrl = 'http://www.google.com';
public function setUp()
{
parent::setUp();
$single_sign_on = Mockery::mock(SingleSignOnApi::class);
$single_sign_on->shouldReceive('getGroupSingleSignOnLink')->andReturn($this->validUrl);
$single_sign_on->shouldReceive('getSingleSignOnLink')->andReturn($this->validUrl);
$this->app->instance(SingleSignOnApi::class, $single_sign_on);
}
//THIS TEST FAILS, SingleSignOnApi Class Not Mocked
public function testGroupAuthConnection()
{
$group = Group::whereNotNull('external_platform_key')->first();
$user = $group->users()->first();
$this->be($user);
$group_sso = $group->groupAuthConnections()->first();
$response = $this->get(route('sso.group.connect', ['id' => $group_sso->id]));
$response->assertRedirect($this->validUrl);
$response->assertSessionMissing('__danger');
}
//THIS TEST PASSES, The SingleSignOnApi Class is Mocked
public function testAuthConnectionConnect()
{
$user = User::first();
$this->be($user);
$sso = $user->authConnections()->firstOrFail();
$response = $this->get(route('sso.connect', ['id' => $sso->id]));
$response->assertRedirect($this->validUrl);
$response->assertSessionMissing('__danger');
}
}
CONTROLLER FUNC - TEST MOCK WORKING
public function connect($id)
{
$auth_connection = $this->findAuthConnection($id, Auth::user());
$sso_client = App::make(SingleSignOnApi::class);
$url = $sso_client->getSingleSignOnLink($auth_connection);
return redirect($url);
}
CONTROLLER FUNC - TEST MOCK NOT WORKING
public function connect($id)
{
$group_ids = Auth::user()->groups()->pluck('groups.id')->toArray();
$group_auth_connection = $this->findGroupAuthConnection($id, Auth::user());
//This is the Mocked Object in my Test: SingleSignOnApi
$sso_client = App::make(SingleSignOnApi::class, [$group_auth_connection->group->external_platform_key]);
$url = $sso_client->getGroupSingleSignOnLink($group_auth_connection, Auth::user());
return redirect($url);
}
I'll use Quickbooks as an example to illustrate how I got this to work consistently for me.
Here's my AppServiceProvider, defining a custom QuickbooksSDK Class I created:
...
public function boot()
{
$this->app->bind(QuickbooksSDK::class, function($app) {
return new QuickbooksSDK(
new GuzzleHttp\Client([
'base_uri' => config('invoicing.baseUri'),
'timeout' => 3,
'headers' => [
'Authorization' => 'Bearer '.config('invoicing.apiKey'),
'Accept' => 'application/json'
],
'http_errors' => false
]),
config('invoicing.apiKey'),
env('QUICKBOOKS_REFRESH_TOKEN'),
env('QUICKBOOKS_CLIENT_ID'),
env('QUICKBOOKS_CLIENT_SECRET')
);
});
}
Then I created a second custom class, called the QuickbooksInvoicingDriver, that takes the instantiated SDK Class from the Service container:
public function __construct()
{
$this->api = app(QuickbooksSDK::class);
}
Finally, in my test class, I can mock the QuickbooksSDK, with my own custom responses, to make testing easier:
$vendorResponse = '{"Vendor": {"Balance": 0,"Vendor1099": false,"CurrencyRef": {"value": "GYD","name": "Guyana Dollar"},"domain": "QBO","sparse": false,"Id": "25","SyncToken": "0","MetaData": {"CreateTime": "2018-04-04T12:36:47-07:00","LastUpdatedTime": "2018-04-04T12:36:47-07:00"},"DisplayName": "daraul","PrintOnCheckName": "daraul","Active": true},"time": "2018-04-04T12:36:47.576-07:00"}';
$mock = new MockHandler([
new Response(200, [], $vendorResponse),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$api = new QuickbooksSDK(
$client,
'test',
'test',
'test',
'test'
);
$this->app->instance(QuickbooksSDK::class, $api);
Now I can run my tests normally, without worrying about third parties. These links were really helpful for me:
http://docs.guzzlephp.org/en/stable/testing.html
https://laravel.com/docs/5.5/container
Let me know if this was helpful.

Testing file upload in lumen 5.5

I'm using Lumen 5.5, and I wrote simple app that upload files.
I wrote test like this (following this tutorial)
<?php
class UploadImageTest extends TestCase
{
Use DatabaseMigrations;
public function testUploadingImageSuccessfully()
{
$this->json('POST', '/images', [
'image' => UploadedFile::fake()->image('image.jpg')
])->assertResponseOk()
}
}
problem is that in my controller, $request->file('image') returns null.
<?php
use Illuminate\Http\Request;
class UploadController extends Controller
{
public function upload(Request $request)
{
if ($request->file('image')) { // always return null
return "File is uploaded!";
}
return "File is not uploaded!";
}
}
I checked other questions (like this one) and tried given solutions with no luck!
I came across this question while searching for the answer to the same problem and wasn't sure it was related, so I posed one relevant to my use case. (Here)
The solution is simple: UploadedFile::fake() doesn't work with JSON, as it fakes a file upload with an XmlHttpRequest (as far as I can tell). You must therefore change your test from this:
public function testUploadingImageSuccessfully()
{
$this->json('POST', '/images', [
'image' => UploadedFile::fake()->image('image.jpg')
])->assertResponseOk()
}
to this:
public function testUploadingImageSuccessfully()
{
$this->call('POST', '/images', [
'image' => UploadedFile::fake()->image('image.jpg')
])->assertResponseOk()
}
Hope it helps!
Please note that passing file argument in the second argument of call() method does not work. Since the second argument is the payload data.
If you pass it as the data, when you use the command on the backend like this:
if($request->hasFile('my_file')) {
// Do some logic here
}
$request->hasFile() will always return false.
You need to pass fake file upload in the 5th argument to make this works.
Here is the method signature of call
call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
this is working for me. just simple. this can submit file upload.
1.condition without authentication
5*1000 is file size (in KB). so i made a test with 5 MB file.
use Faker\Factory as Faker;
use Illuminate\Http\UploadedFile;
class SampleTest extends TestCase
{
public function testItCanCreateUser()
{
$faker = Faker::create();
$files = [
'file' => UploadedFile::fake()->create('file.jpg', 5*1000)
];
$response = $this->call('POST', '/chunk', [], [], $files);
$this->assertEquals(200, $response->getStatusCode());
}
}
2.condition with authentication (logged in user)
use Faker\Factory as Faker;
use Illuminate\Http\UploadedFile;
class SampleTest extends TestCase
{
public function testItCanUpdateProfileUser()
{
$faker = Faker::create();
$files = [
'file' => UploadedFile::fake()->create('file.jpg', 5*1000)
];
$headers = [
'Accept' => 'application/json',
'Authorization' => 'your-jwt-token'
];
$servers = [];
foreach ($headers as $k => $header) {
$servers["HTTP_" . $k] = $header;
}
$response = $this->call('POST', '/chunk', [], [], $files, $servers);
$this->assertEquals(200, $response->getStatusCode());
}
}
you need to add HTTP_ on every request header. i'm not sure why. but it will work.

How to get matched route name in View - Zend Expressive

I know that I can generate URL passing the route name
<?php echo $this->url('route-name') #in view file ?>
But can I get information in opposite direction?
From current URL/URI, I need to get route name.
Real case is: I have layout.phtml where is the top menu (html).
Current link in the menu need to be marked with css class. So, example what I need is:
<?php // in layout.phtml file
$index_css = $this->getRouteName() == 'home-page' ? 'active' : 'none';
$about_css = $this->getRouteName() == 'about' ? 'active' : 'none';
$contact_css = $this->getRouteName() == 'contact' ? 'active' : 'none';
?>
I am using fast route, but I am interesting in any solution. Solution doesn't have to be in View file.
From my research, there is such information in RouteResult instance in the public method getMatchedRouteName(). The problem is how to reach to this instance from the View.
We know that we can get RouteResult, but from the Request object which is in a Middleware's __invoke() method.
public function __invoke($request, $response, $next){
# instance of RouteResult
$routeResult = $request->getAttribute('Zend\Expressive\Router\RouteResult');
$routeName = $routeResult->getMatchedRouteName();
// ...
}
As #timdev recommended we will find inspiration in existing helper UrlHelper and make almost the same implementation in custom View Helper.
In short we will create 2 classes.
CurrentUrlHelper with method setRouteResult() and
CurrentUrlMiddleware with __invoke($req, $res, $next)
We will inject the CurrentUrlHelper in CurrentUrlMiddleware and
in __invoke() method call the CurrentUrlHelper::setRouteResult() with appropriate RouteResult instance.
Later we can use our CurrentUrlHelper with RouteResult instance in it. Both classes should have an Factory too.
class CurrentUrlMiddlewareFactory {
public function __invoke(ContainerInterface $container) {
return new CurrentUrlMiddleware(
$container->get(CurrentUrlHelper::class)
);
}
}
class CurrentUrlMiddleware {
private $currentUrlHelper;
public function __construct(CurrentUrlHelper $currentUrlHelper) {
$this->currentUrlHelper = $currentUrlHelper;
}
public function __invoke($request, $response, $next = null) {
$result = $request->getAttribute('Zend\Expressive\Router\RouteResult');
$this->currentUrlHelper->setRouteResult($result);
return $next($request, $response); # continue with execution
}
}
And our new helper:
class CurrentUrlHelper {
private $routeResult;
public function __invoke($name) {
return $this->routeResult->getMatchedRouteName() === $name;
}
public function setRouteResult(RouteResult $result) {
$this->routeResult = $result;
}
}
class CurrentUrlHelperFactory{
public function __invoke(ContainerInterface $container){
# pull out CurrentUrlHelper from container!
return $container->get(CurrentUrlHelper::class);
}
}
Now we only need to register our new View Helper and Middleware in the configs:
dependencies.global.php
'dependencies' => [
'invokables' => [
# dont have any constructor!
CurrentUrlHelper::class => CurrentUrlHelper::class,
],
]
middleware-pipeline.global.php
'factories' => [
CurrentUrlMiddleware::class => CurrentUrlMiddlewareFactory::class,
],
'middleware' => [
Zend\Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
Zend\Expressive\Helper\UrlHelperMiddleware::class,
CurrentUrlMiddleware::class, # Our new Middleware
Zend\Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],
And finaly we can register our View Helper in templates.global.php
'view_helpers' => [
'factories' => [
# use factory to grab an instance of CurrentUrlHelper
'currentRoute' => CurrentUrlHelperFactory::class
]
],
it's important to register our middleware after ROUTING_MIDDLEWARE and before DISPATCH_MIDDLEWARE!
Also, we have CurrentUrlHelperFactory only to assign it to the key 'currentRoute'.
Now you can use helper in any template file :)
<?php // in layout.phtml file
$index_css = $this->currentRoute('home-page') ? 'active' : 'none';
$about_css = $this->currentRoute('about') ? 'active' : 'none';
$contact_css = $this->currentRoute('contact') ? 'active' : 'none';
?>
As you note in your self-answer, UrlHelper is a useful thing to notice. However, creating a new helper that depends on UrlHelper (and reflection) isn't ideal.
You're better off writing your own helper, inspired UrlHelper but not dependent on it.
You can look at the code for UrlHelper, UrlHelperFactory and UrlHelperMiddleware to inform your own implementation.
You could wrap the template renderer in another class and pass the Request to there, subtract what you need and inject it into the real template renderer.
Action middleware:
class Dashboard implements MiddlewareInterface
{
private $responseRenderer;
public function __construct(ResponseRenderer $responseRenderer)
{
$this->responseRenderer = $responseRenderer;
}
public function __invoke(Request $request, Response $response, callable $out = null) : Response
{
return $this->responseRenderer->render($request, $response, 'common::dashboard');
}
}
The new wrapper class:
<?php
declare(strict_types = 1);
namespace Infrastructure\View;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Zend\Diactoros\Stream;
use Zend\Expressive\Router\RouteResult;
use Zend\Expressive\Template\TemplateRendererInterface;
class ResponseRenderer
{
private $templateRenderer;
public function __construct(TemplateRendererInterface $templateRenderer)
{
$this->templateRenderer = $templateRenderer;
}
public function render(Request $request, Response $response, string $templateName, array $data = []) : Response
{
$routeResult = $request->getAttribute(RouteResult::class);
$data['routeName'] = $routeResult->getMatchedRouteName();
$body = new Stream('php://temp', 'wb+');
$body->write($this->templateRenderer->render($templateName, $data));
$body->rewind();
return $response->withBody($body);
}
}
Code is borrowed from GitHub.

How do I access the Router through an artisan command?

I need access to the RouteCollection that Laravel possesses when it gets ran normally and all ServiceProviders are booted. I need the RouteCollection because a legacy app needs to know if Laravel has the particular route, so if it doesn't, legacy code can take care of the route.
I figure if I can somehow get a hold of Illuminate\Routing\Router in an artisan command, I could simply call the getRoutes() function and output a JSON file containing an array of all the routes. Then when the legacy code needs to determine if Laravel supports the Route, it could read that file.
In order to do that though, I need access to the Router class. Not sure how to accomplish that... I looked at Illuminate\Foundation\Console\RoutesCommand source code and I can't figure out how it works. What's really odd is it looks like the Router class is being injected, but when I do Artisan::resolve('MyCommand'), I get an empty RouteCollection.
EDIT
I never did figure out how to accomplish this question, but for those in a similar situation, I found this works for the most part, although I'm not sure how bad the overhead is starting Laravel each request just to check the routes. Right now it doesn't seem like that much.
// Start laravel, so we can see if it should handle the request
require_once(__DIR__.'/laravel/bootstrap/autoload.php');
$app = require_once(__DIR__.'/laravel/bootstrap/start.php');
$app->boot();
// Create the fake request for laravel router
$request = Request::createFromGlobals();
$request->server->set('PHP_SELF', '/laravel/public/index.php/'.$_REQUEST['modname']);
$request->server->set('SCRIPT_NAME', '/laravel/public/index.php');
$request->server->set('SCRIPT_FILENAME', __DIR__.'/laravel/public/index.php');
$request->server->set('REQUEST_URI', '/laravel/public/'.$_REQUEST['modname']);
$routes = Route::getRoutes();
foreach($routes as $route) {
if ($route->matches($request)) {
$app->run($request);
exit;
}
}
Here is a simplified implementation
JsonRoutes.php
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
class JsonRoutes extends Command {
protected $name = 'routes:json';
protected $description = 'Spits route information in JSON format.';
protected $router;
protected $routes;
public function __construct(Router $router) {
parent::__construct();
$this->router = $router;
$this->routes = $router->getRoutes();
}
public function fire() {
$result = [];
foreach ($this->routes as $route) {
$result[] = [
'methods' => implode(',', $route->methods()),
'host' => $route->domain(),
'uri' => $route->uri(),
'name' => $route->getName(),
'action' => $route->getActionName(),
];
}
$file = $this->option('file');
if ($file) {
File::put($file, json_encode($result));
return;
}
$this->info(json_encode($result));
}
protected function getArguments() { return []; }
protected function getOptions() {
return [
['file', 'f', InputOption::VALUE_OPTIONAL, 'File name.'],
];
}
}
You register it in artisan.php:
Artisan::resolve('JsonRoutes');
Sample usage:
Spit it out to stdout
$ artisan routes:json
[{"methods":"GET,HEAD","host":null,"uri":"\/","name":null,"action":"Closure"}]
Write it to a file
$ artisan routes:json -f /tmp/routes.json

Categories