Tenant Multi tenancy Laravel Unit Testing issue - php

Here is the test case that i created for category testing. I am getting 404 on this route while i have correctly configured the tenant test case and this route is exist on subdomain that was created on chrome browser.
public function test_example()
{
$response = $this->call('GET', '/categories/6/edit');
$this->assertEquals(200, $response->getStatusCode(),$response->exception->getMessage());
}
My TestCase.php
protected $tenancy = false;
public function setUp(): void
{
parent::setUp();
if ($this->tenancy) {
$this->initializeTenancy();
}
}
public function initializeTenancy()
{
$tenant = Tenant::create();
tenancy()->initialize($tenant);
}
Documentation I am following
https://tenancyforlaravel.com/docs/v3/testing
Result:
I want 302 response means redirect to login code.

I hope this piece of code can help you
https://github.com/archtechx/tenancy/issues/635#issuecomment-939226522
tenancy()->initialize($tenant);
URL::forceRootUrl('http://' . $tenant->domains[0]['domain']);

Related

Laravel PHPUnit unable to test GET parameters

I have a Laravel 8 app that I'm working on where I need to use GET parameters (e.g. test?num=1) but unfortunately, I don't appear to be able to figure out how to run the unit tests against this.
I've followed the guidance I can find online (including Laravel phpunit testing get with parameters, which looked very promising) however, none of the approaches seem to work.
To try and figure out the issue, I've created a VERY simple version and I'm getting the same behaviours. If I go to the URL directly it works as expected but the unit tests fail.
routes\web.php
Route::get('/test', 'TimeController#test')->name('myTest');
app\Http\Controllers\TimeController.php
public function test()
{
return $_GET['num'] ?? "Num not provided";
}
test\Unit\Http\Controllers\TimeControllerTest.php
/** #test */
public function num_test_1()
{
$params = ["num" => "1"];
$response = $this->get('/test?num=1');
$response->assertSeeText("1");
}
/** #test */
public function num_test_2()
{
$params = ["num" => "1"];
$response = $this->get('/test', $params);
$response->assertSeeText("1");
}
/** #test */
public function num_test_3()
{
$params = ["num" => "1"];
$response = $this->call('GET', '/test', $params);
$response->assertSeeText("1");
}
All three of the above tests fail with the message
Failed asserting that 'Num not provided' contains "1".
I have even gone to the lengths of deleting the node_modules and vendor folders just in case I had a corrupt PHPUnit package or something.
Am I missing anything obvious? This feels like something that should be pretty easy to do and something that a lot of people will have done before.
It work fine.
You should change your method like this:
public function test(Request $request)
{
return $request->input('num') ?? "Num not provided";
}
Or with query method:
public function test(Request $request)
{
return $request->query('num') ?? "Num not provided";
}
You can also pass your desired default value if none found instead of using ??:
public function test(Request $request)
{
return $request->input('num', 'Num not provided');
}
public function test(Request $request)
{
return $request->query('num', 'Num not provided');
}

Laravel 'can' middleware return error 500 "This action is unauthorized"

i'm trying to create a website based on laravel framework. I'm stuck in permission control with Policy. This is my code:
+ Policies Register in App\Providers\AuthServiceProvider:
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
User::class => UserPolicy::class,
Category::class => CategoryPolicy::class
];
public function boot()
{
$this->registerPolicies();
try {
Permission::all()->each(function($permission) {
Gate::define($permission->name, function ($user) use($permission) {
return $user->hasPermission($permission->name);
});
});
} catch (\Exception $e) {
Log::notice('Unable to register gates. Either no database connection or no permissions table exists.');
}
}
}
User Policy in App\Policies\UserPolicy
class UserPolicy
{
use HandlesAuthorization;
public function viewAny(User $user)
{
return true;
}
public function view(User $user, User $target_user)
{
return $user->id === $target_user->id;
}
}
Api Route in routes/api.php
Route::get('/users', 'Api\UserController#getUsers')->middleware('can:view-users');
Route::get('/users/{user_id}', 'Api\UserController#getUser')->middleware('can:view-users');
Route::put('/users/{user_id}', 'Api\UserController#updateUser')->middleware('can:edit-users');
User Controller in App\Http\Controllers\Api\UserController
public function getUsers(UserRequest $request) {
$users = $this->userRepository->getAll();
$this->authorize('view', $user);
return response($users, 200);
}
public function getUser(UserRequest $request, $user_id) {
$user = $this->userRepository->find($user_id);
$this->authorize('view', $user);
return response($user, 200);
}
When I try to get data by using 2 url above, it returned error 500 Internal Server Error even when user is authenticated :
{
"error": "This action is unauthorized."
}
I tried to log result and found that error come from middleware can or Illuminate\Auth\Middleware\Authorize.
Code line:
$this->gate->authorize($ability, $this->getGateArguments($request, $models)); in handle() function throw error above.
After hours searching, I could not figure out solution.
Anyone can point out my mistake?
Solved. After hours reading source code in library, I figured out problem. I used api guard for my app without passport module. Thus, variable $userResolver in Illuminate\Auth\Access\Gate class could not be set to authenticated user and got null value. To solve this problem, I created my own Authorize middleware instead of laravel middleware Authorize and use Auth::guard('api')->user() to get authenticated user.

Laravel & phpunit set expected http error code

I'm writing some tests. Here is my test:
/** #test */
public function a_normal_user_cannot_access_the_admin_panel()
{
// Note: This is a regular user, not an admin
$user = factory(User::class)->create();
$this->actingAs($user);
$this->visit('/admin');
// ????
}
In my MustBeAdministrator.php middleware:
public function handle($request, Closure $next)
{
$user = $request->user();
if ($user && $user->isAdmin) {
return $next($request);
}
abort(403);
}
When i visit /admin, the middleware aborts with a 403 error. How can i assert with phpunit that an http error was thrown? I know about $this->setExpectedException(), but i can't get it to work with http error. Am i doing this wrong?
NOTE: I'm very new to phpunit and also exceptions, so sorry if this is a stupid question. Here is the repo for this project if you need any other files, or you can just ask.
$user = factory(User::class)->create();
$this->actingAs($user);
$this->setExpectedException('Symfony\Component\HttpKernel\Exception\HttpException');
$this->get('/admin');
throw $this->response->exception;
Found this article. Adding the setExpectedException line, the throw line, and changing visit to get seemed to solve my problem
if you want to get the full response object, you may use the call method
/** #test */
public function a_normal_user_cannot_access_the_admin_panel()
{
// Note: This is a regular user, not an admin
$user = factory(User::class)->create();
$this->actingAs($user);
$response = $this->call('GET', '/admin');
$this->assert(403, $response->status());
}

Slim 3 Middleware Redirect

I want to check if a user is logged in. Therefor I have an Class witch returns true or false. Now I want a middleware which checks if the user is logged in.
$app->get('/login', '\Controller\AccountController:loginGet')->add(Auth::class)->setName('login');
$app->post('/login', '\Controller\AccountController:loginPost')->add(Auth::class);
Auth Class
class Auth {
protected $ci;
private $account;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
$this->account = new \Account($this->ci);
}
public function __invoke($request, \Slim\Http\Response $response, $next) {
if($this->account->login_check()) {
$response = $next($request, $response);
return $response;
} else {
//Redirect to Homepage
}
}
}
So when the user is logged in the page will render correctly. But when the user is not autoriesed I want to redirect to the homepage. But how?!
$response->withRedirect($router->pathFor('home');
This doesn't work!
You need to return the response. Don't forget that the request and response objects are immutable.
return $response = $response->withRedirect(...);
I have a similar auth middleware and this is how I do it which also adds a 403 (unauthorized) header.
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);
Building off of tflight's answer, you will need to do the following to make everything work as intended. I tried to submit this as a revision, given that the code provided in tflight's answer would not work on the framework out of the box, but it was declined, so providing it in a separate answer:
You will need the following addition to your middleware:
protected $router;
public function __construct($router)
{
$this->router = $router;
}
Additionally, when declaring the middleware, you would need to add the following the constructor:
$app->getContainer()->get('router')
Something similar to:
$app->add(new YourMiddleware($app->getContainer()->get('router')));
Without these changes, the solution will not work and you will get an error that $this->router does not exist.
With those changes in place you can then utilize the code provided by tflight
$uri = $request->getUri()->withPath($this->router->pathFor('home'));
return $response = $response->withRedirect($uri, 403);
make basic Middleware and inject $container into it so all your middleware can extends it.
Class Middleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __get($property)
{
if (isset($this->container->{$property})) {
return $this->container->{$property};
}
// error
}
}
make sure your Auth middleware on the same folder with basic middleware or you can use namespacing.
class Auth extends Middleware
{
public function __invoke($request, $response, $next)
{
if (!$this->account->login_check()) {
return $response->withRedirect($this->router->pathFor('home'));
}
return $next($request, $response);
}
}
Use:
http_response_code(303);
header('Location: ' . $url);
exit;

Add Custom function to Auth Class Laravel (Extends Guard Class)

I had modified a vendor file of Laravel placed at
/vendor/laravel/framework/src/Illuminate/Auth/Guard.php
but it will be overwritten upon updating Laravel.
I'm looking for a way to put the code somewhere in my /app to prevent the overwrite.
The function modified is
public function UpdateSession() {
$this->session->set('type', $type); //==> Set Client Type
}
Also there's a new function on the file:
public function type() {
return $this->session->get('type'); //==> Get Client Type
}
Codes above are called in many places in my application.
Any idea?
Steps:
1- create myGuard.php
class myGuard extends Guard
{
public function login(Authenticatable $user, $remember = false)
{
$this->updateSession($user->getAuthIdentifier(), $user->type);
if ($remember) {
$this->createRememberTokenIfDoesntExist($user);
$this->queueRecallerCookie($user);
}
$this->fireLoginEvent($user, $remember);
$this->setUser($user);
}
protected function updateSession($id, $type = null)
{
$this->session->set($this->getName(), $id);
$this->session->set('type', $type);
$this->session->migrate(true);
}
public function type()
{
return $this->session->get('type');
}
}
2- in AppServiceProvider or new service provider or routes.php:
public function boot()
{
Auth::extend(
'customAuth',
function ($app) {
$model = $app['config']['auth.model'];
$provider = new EloquentUserProvider($app['hash'], $model);
return new myGuard($provider, App::make('session.store'));
}
);
}
3- in config/auth.php
'driver' => 'customAuth',
4- now you can use this
Auth::type();
This doesn't look like you need to update the Guard at all. As far as I can see you are only trying to retrieve data from the session. And that's definitely no business for the Guard itself.
You have multiple ways of accessing the session yourself:
// via Session-Facade
$type = Session::get('type');
Session::put('type', $type);
// via Laravels helper function
$type = session('type'); // get
session()->put('type', $type); // set
session(['type' => $type']); // alternative

Categories