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.
Related
Basically, this is Laravel-PHP website. At first, we make a page that showing tables from database with API and its success. Next, we want to put "searching feature" that can be used to search the specific(%like%) table's name but it doesn't work. Here is the example :
Taking tables data from database with API (guzzle) source codes :
a. source codes
<?php
namespace App\Http\Controllers;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class table extends Controller
{
public $ambilTable;
public function __construct()
{
$this->ambilTable = new Client([
'base_uri' => 'http://20.20.20.200:8585/api/v1/',
]);
}
public function index(Request $request)
{
$response = $this->ambilTable->request('GET', 'tables', [
'query' => [
'limit' => '14',
],
]);
$data = json_decode($response->getBody()->getContents(), true)['data'];
return view('table.index', compact('data'));
}
b. Result
Searching feature on the page (we have makes some of source codes, but nothing work), here is the one of them:
<?php
namespace App\Http\Controllers;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class table extends Controller
{
public $ambilTable;
public function __construct()
{
$this->ambilTable = new Client([
'base_uri' => 'http://20.20.20.200:8585/api/v1/',
]);
}
public function index(Request $request)
{
$response = $this->ambilTable->request('GET', 'tables', [
'query' => [
'limit' => '14',
],
]);
$search = $request->search;
if ($request ->has('search')) {
$take = $response->where('name','like','%' .$search.'%')->get();
} else {
}
return view('table.index', compact('data'));
}
Those source codes, nothing work. Do someone have any solution, advice, or tutorial link, please? Thank You
I have tried several ways to achieve this, but none seems to work,
it seems CodeIgniter 4 does not have the ability to apply multiple filters to a single route, currently here is what I am trying:
ProvideInfoFilter.php:
<?php namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class ProvideinfoFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
echo "pinfo";
$account_data = new \App\Libraries\Account_Data;
return $account_data->no_info_redirect();
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
}
AccessFilter.php:
<?php namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Filters\FilterInterface;
class AccessFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$account_data = new \App\Libraries\Account_Data;
echo "accessf";
if ($request->uri->getSegment(1) !== 'm' && $request->uri->getSegment(2) !== 'm' && !$request->getGet('token'))
{
return $account_data->is_logged_in();
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
}
}
Filters.php:
<?php namespace Config;
use CodeIgniter\Config\BaseConfig;
class Filters extends BaseConfig
{
public $aliases = [
'toolbar' => \CodeIgniter\Filters\DebugToolbar::class,
'accesscontrol' => \App\Filters\AccessFilter::class,
'provide_info' => \App\Filters\ProvideinfoFilter::class
];
public $globals = [
'before' => [
],
'after' => [
'toolbar'
],
];
public $methods = [];
public $filters = [
'provide_info' => ['before' => ['user', 'user/*']],
'accesscontrol' => ['before' => ['user', 'user/*']]
];
}
I have added echo statements for debugging
The problem is, Commenting out either 'accesscontrol' => ['before' => ['user', 'user/*']] or 'provide_info' => ['before' => ['user', 'user/*']] applies either of the filters the and the echoed string can be seen in the output. But having both of them like demonstrated above does not apply both of them.
It is important for me to have both filters running as I want to apply specific exemptions for each of them using the $globals array.
If you return a Responce instance in a before filter it will be sent back to the client and the script execution will stop. https://codeigniter.com/user_guide/incoming/filters.html#before-filters
It's normal then that your second filter doesn't run.
In general, avoid to return anything in a before filter if you want it to not stop your script.
You are already created 2 filters which are right after that you have to create a alias of both filters on Filter.php file.
After that you have to open app/config/Feature.php file and chage this:
public $multipleFilters = false;
To
public $multipleFilters = true;
After that you go to app/config/routes.php file and then
if you create single route then
$routes->get('home', 'Dashboard::index', ['filter' => ['ProvideinfoFilter','AccessFilter']]);
In case you want to use group then:
$routes->group('msp', ['filter' => ['authGuard','AdminAuth']], function($routes){
$routes->get('/', 'MSPController::msp');
$routes->get('list_data', 'MSPController::list_data');
});
For more information follow this Codeigniter 4 Doc
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.
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
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