PHPunit: can't assert API login test - phpunit

I'm trying to follow best practices using TDD to build the auth logic with Sanctum. But haven't been able to pass the login test.
Using postman the route works well:
But the test is not behaving as expected. It'd simply fail.
1) Tests\Feature\LoginTest::test_login_route_api
Expected status code 200 but received 401.
Failed asserting that 200 is identical to 401.
I tried to pass a token as header or body and it has not helped.
The test
public function test_login_route_api()
{
$this->withoutExceptionHandling();
//using RefreshDatabase
$user = User::factory()->create();
$response = $this->post('/api/login', [
'email' => $user->email,
'password' => $user->password,
], []);
$response->dumpHeaders();
$response->dumpSession();
$response->dump();
$response->assertStatus(200);
LoginController (method as advised in Laravel Documentation https://laravel.com/docs/8.x/authentication#authenticating-users):
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
// $request->session()->regenerate();
$user = Auth::user();
// $success['token'] = $user->createToken('MyApp')->plainTextToken;
$success['token'] = $request->token;
$success['name'] = $user->name;
return response([$success, 'logged in'], 200);
}
return response('fail!', 401);
}
The route:
Route::name('api')->group(function () {
Route::post('/login', [LoginController::class, 'login']);
// some routes
});
phpunit.xml uncommented
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
Not sure what's going on, I've read some similar topics but none has helped me.
Thank you,

Ok, solved it.
Somehow, using factory()->make() or factory()->create() with RefreshDatabase wasn't accepting the $user as valid $credentials.
I had to use an user from with a seeded database like so: $user = User::first();
This would make the test pass.
Any idea why factory users are not passing the test?

Related

Google analytics V4 client access to our app

We have a web application in PHP, for our clients we have prepared connect to google analytics UA. I use "google/apiclient": "^2.0", it works that our clients click on button in our administration and then is runned a followed code:
$this->client = new Google_Client();
$this->client->setApplicationName("xxxx");
$this->client->setClientId("xxxx");
$this->client->setClientSecret("xxxx");
$this->client->setScopes(array("https://www.googleapis.com/auth/analytics.readonly"));
$this->client->setRedirectUri("xxxx");
$this->client->setAccessType('offline');
$this->client->setApprovalPrompt("force");
The credentials i get from https://console.cloud.google.com/ -> OAuth 2.0 Client IDs
then the client is redirected to google where he log in, and allow acces to his GA data for our app. then is redirected back with code is generated access token. With this token i can get his GA UA data and show it to graphs in our administration. It works allright, but now i get information that GA UA will be end, and i need to create the same proces for UA V4. But in documentation to GA V4 what i found: https://developers.google.com/analytics/devguides/reporting/data/v1
Is not information how to process it for our clients. There is only authorisation over service account, that i must donwload my own credentials.json to service account but it allow me only acces to my private account, but i need it to work the same as before, so for other clients without having to upload credentials.json. That is, to be redirected to google via OAuth 2.0 Client IDs and allow access to our application to read their data. Is it even possible?
Thank you for help, and sorry for my bad english
This should give you a start. I am combining the OAuth2 methods from the Google API php client library and applying them to the new library.
Its not optimal but it works. Code below is for installed application not web. Its not going to work hosted.
<?php
require 'vendor/autoload.php';
use Google\Client;
use Google\Analytics\Data\V1beta\BetaAnalyticsDataClient;
putenv('GOOGLE_APPLICATION_CREDENTIALS=C:\YouTube\dev\credentials.json'); // Installed / native / desktop Client credetinals.
$credentials = getenv('GOOGLE_APPLICATION_CREDENTIALS');
$myfile = file_get_contents($credentials, "r") ;
$clientObj = json_decode($myfile);
$client = getClient();
$tokenResponse = $client->getAccessToken();
print_r($tokenResponse);
print_r($tokenResponse["access_token"]);
$service = new BetaAnalyticsDataClient( [
'credentials' => Google\ApiCore\CredentialsWrapper::build( [
'scopes' => [
'https://www.googleapis.com/auth/analytics',
'openid',
'https://www.googleapis.com/auth/analytics.readonly',
],
'keyFile' => [
'type' => 'authorized_user',
'client_id' => $clientObj->installed->client_id,
'client_secret' => $clientObj->installed->client_secret,
'refresh_token' => $tokenResponse["refresh_token"]
],
] ),
] );
$response = $service->runReport([
'property' => 'properties/[YOUR_PROPERTY_ID]'
]);
foreach ($response->getRows() as $row) {
foreach ($row->getDimensionValues() as $dimensionValue) {
print 'Dimension Value: ' . $dimensionValue->getValue() . PHP_EOL;
}
}
function getClient()
{
$client = new Client();
$client->setApplicationName('Google analytics data beta Oauth2');
$client->setScopes('https://www.googleapis.com/auth/analytics');
$client->setAuthConfig(getenv('GOOGLE_APPLICATION_CREDENTIALS'));
$client->setAccessType('offline');
// Load previously authorized token from a file, if it exists.
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
$tokenPath = 'token.json';
if (file_exists($tokenPath)) {
$accessToken = json_decode(file_get_contents($tokenPath), true);
$client->setAccessToken($accessToken);
}
// If there is no previous token or it's expired.
if ($client->isAccessTokenExpired()) {
// Refresh the token if possible, else fetch a new one.
if ($client->getRefreshToken()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
} else {
// Request authorization from the user.
$authUrl = $client->createAuthUrl();
printf("Open the following link in your browser:\n%s\n", $authUrl);
print 'Enter verification code: ';
$authCode = trim(fgets(STDIN));
// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);
// Check to see if there was an error.
if (array_key_exists('error', $accessToken)) {
throw new Exception(join(', ', $accessToken));
}
}
// Save the token to a file.
if (!file_exists(dirname($tokenPath))) {
mkdir(dirname($tokenPath), 0700, true);
}
file_put_contents($tokenPath, json_encode($client->getAccessToken()));
}
return $client;
}
And please how to get list streams of listed properties, I have a code to get GA4 properties:
$this->SetGa4ClientAdmin();
$accounts = $this->ga4_admin->listAccounts();
$this->data['accounts_v4'] = array();
foreach ($accounts as $account) {
$this->data['accounts_v4'][$account->getName()] = array('name' => $account->getDisplayName(), 'childrens' => array());
try {
$properties = $this->ga4_admin->ListProperties('parent:' . $account->getName());
foreach ($properties AS $property) {
$this->data['accounts_v4'][$account->getName()]['childrens'][$property->getName()] = $property->getDisplayName();
}
} catch (Exception $ex) {
die("error: " . $ex->getMessage());
}
}
At every property I need to get measurement ID of GA4 streams.
I need to get this

What can I do to resolve this pusher error-JSON returned from auth endpoint was invalid, yet status code was 200?

I still have this problem after asking the same question here: JSON returned from auth endpoint was invalid, yet status code was 200 with no response. I've looked at similar questions and followed the
suggestions: setting my broadcast driver to 'pusher', uncommenting 'App/BroadcastServiceProvider' class in my app.config file, setting debug mode to false in my .env file, etc. I have also looked at pusher docs but the issue remains unresolved for me.
I have updated my previous attempt by adding '/broadcasting/auth/' auth endpoint and headers but still the same error. But I can now see a 302 redirect to the auth route then a 302 redirect to the login route then to the dashboard with a 200 response on laravel telescope, which I wasn't seeing before now. So this suggests to me that adding the auth endpoint ought to resolve the issue but it doesn't.
I also tried setting up and using a '/pusher/auth/' auth end point route and controller but it gave me a 'Failed to load resource: the server responded with a status of 405 (Method Not Allowed)' along with "Error: Unable to retrieve auth string from auth endpoint - received status: 405 from /pusher/auth, but not the previous invalid json error. I get this with a 'get' request to the controller but a 500-internal server error with a 'post' request. I really don't know which is correct.
This is my bootstrap.js file:
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
// Enable pusher logging - don't include this in production
Pusher.logToConsole = true;
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true,
authEndpoint: '/broadcasting/auth',
//authEndpoint: '/pusher/auth',
auth: {
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
}
}
});
This is one pusherController I created:
public function pusherAuth(Request $request)
{
$key = getenv('PUSHER_APP_KEY');
$secret = getenv('PUSHER_APP_SECRET');
$app_id = getenv('PUSHER_APP_ID');
$pusher = new Pusher($key, $secret, $app_id);
$auth = $pusher->socket_auth($_GET('channel_name'), $_GET('socket_id'));
return response($auth, 200);
}
I now know my vue frontend file that should receive and display the broadcast checks out and the issue has to do with resolving this pusher subscription error.
Any help will be appreciated.
Check your .env for the correct Broadcast driver:
BROADCAST_DRIVER=pusher
I was finally able to resolve this issue. The problem was entirely an authentication issue as the error messages pointed out. While I still don't know why the built in '/broadcast/auth' endpoint didn't work, my initial attempt to authenticate by creating a '/pusher/auth/' was wrong in the way I set up the route and controller.
The correct route set up should be 'post' and call a controller, using a closure based route didn't work for me. My previous (see above) implementation of the controller was also wrong.
This is the controller code that worked:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Pusher\Pusher;
class PusherController extends Controller
{
/**
* Authenticates logged-in user in the Pusher JS app
* For private channels
*/
public function pusherAuth(Request $request)
{
$user = auth()->user();
$socket_id = $request['socket_id'];
$channel_name =$request['channel_name'];
$key = getenv('PUSHER_APP_KEY');
$secret = getenv('PUSHER_APP_SECRET');
$app_id = getenv('PUSHER_APP_ID');
if ($user) {
$pusher = new Pusher($key, $secret, $app_id);
$auth = $pusher->socket_Auth($channel_name, $socket_id);
return response($auth, 200);
} else {
header('', true, 403);
echo "Forbidden";
return;
}
}
}
This is the final bootstrap.js file:
import Echo from 'laravel-echo';
window.Pusher = require('pusher-js');
// Enable pusher logging - don't include this in production
Pusher.logToConsole = true;
window.Echo = new Echo({
broadcaster: 'pusher',
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true,
authEndpoint: '/pusher/auth',
auth: {
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
}
}
});
And my route code in web.php:
Route::post('/pusher/auth', [PusherController::class, 'pusherAuth'])
->middleware('auth');
Pusher console log:
Pusher : : ["Event recd",{"event":"pusher_internal:subscription_succeeded","channel":"private-user.3","data":{}}]
vendor.js:41325 Pusher : : ["No callbacks on private-user.3 for pusher:subscription_succeeded"]

Laravel Echo + Pusher: request to broadcasting/auth returns login page

I updated my application from 5.2 to 5.3 to broadcast notifications with Pusher, but when pusher try to authenticate current logged in user over broadcasting/auth, I got an error:
Pusher : No callbacks on private-App.Models.Client.9 for pusher:subscription_error
in the browser console->network->xhr, I found that the request to broadcasting/auth not giving me the auth:{token} object, but returning my login page instead !!!
I think it is a problem with middleware but I can't find it.
BroadcastServiceProvider.php:
public function boot()
{
// Broadcast::routes();
Broadcast::routes(['middleware' => ['auth:client']]);
/*
* Authenticate the user's personal channel...
*/
Broadcast::channel('App.Models.Client.*', function ($user, $userId) {
return true;
});
}
app.js: after importing pusher-js & Laravel Echo
$(document).ready(function() {
// check if there's a logged in user
if(Laravel.clientId) {
window.Echo.private(`App.Models.Client.${Laravel.clientId}`)
.notification((notification) => {
console.log(notification);
addNotifications([notification], '#notifications');
});
} });
any help would be much appreciated!
With Laravel version > 5.3 & Pusher, you need add token in header request, your code in resources/assets/js/bootstrap.js
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your key',
cluster: 'your cluster',
encrypted: true,
auth: {
headers: {
Authorization: 'Bearer ' + YourTokenLogin
},
},
});
it worked for me, and hope it help you.
After a long time of pulling my hair, I finally found the cause of this issue. I was using a package for Authentication/Authorization called cartalyst/sentinel which is great except that once installed it will entirely replace the Laravel default auth behavior. So any request to broadcasting/auth is actually rejected.

laravel phpunit withexceptionhandling

I'm in the process of writing a web app using Laravel 5.5 and Vue.js. PHPUnit version is 6.3.1.
I'm testing for validation errors when a user registers using Form Requests.
Route:
// web.php
Route::post('/register', 'Auth\RegisterController#store')->name('register.store');
This is my passing test:
/** #test */
function validation_fails_if_username_is_missing()
{
$this->withExceptionHandling();
$this->json('POST', route('register.store'), [
'email' => 'johndoe#example.com',
'password' => 'secret',
'password_confirmation' => 'secret'
])->assertStatus(422);
}
However, it fails when I remove exception handling:
/** #test */
function validation_fails_if_username_is_missing()
{
$this->json('POST', route('register.store'), [
'email' => 'johndoe#example.com',
'password' => 'secret',
'password_confirmation' => 'secret'
])->assertStatus(422);
}
I do not understand why this test fails without exception handling as it's stated in the Laravel documentation that
If the request was an AJAX request, a HTTP response with a 422 status
code will be returned
I already tried to declare this particular route in the api middleware group, but that didn't change anything.
Can someone with more experience than I do explain to me why that is? Thanks in advance.
EDIT: This is the content of my Handler.php class file. I don't think anything was edited.
protected $dontReport = [
\Illuminate\Auth\AuthenticationException::class,
\Illuminate\Auth\Access\AuthorizationException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class,
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
];
public function report(Exception $exception)
{
parent::report($exception);
}
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}

Batch requests on Symfony

I am trying to reproduce the behaviour of the facebook batch requests function on their graph api.
So I think that the easiest solution is to make several requests on a controller to my application like:
public function batchAction (Request $request)
{
$requests = $request->all();
$responses = [];
foreach ($requests as $req) {
$response = $this->get('some_http_client')
->request($req['method'],$req['relative_url'],$req['options']);
$responses[] = [
'method' => $req['method'],
'url' => $req['url'],
'code' => $response->getCode(),
'headers' => $response->getHeaders(),
'body' => $response->getContent()
]
}
return new JsonResponse($responses)
}
So with this solution, I think that my functional tests would be green.
However, I fill like initializing the service container X times might make the application much slower. Because for each request, every bundle is built, the service container is rebuilt each time etc...
Do you see any other solution for my problem?
In other words, do I need to make complete new HTTP requests to my server to get responses from other controllers in my application?
Thank you in advance for your advices!
Internally Symfony handle a Request with the http_kernel component. So you can simulate a Request for every batch action you want to execute and then pass it to the http_kernel component and then elaborate the result.
Consider this Example controller:
/**
* #Route("/batchAction", name="batchAction")
*/
public function batchAction()
{
// Simulate a batch request of existing route
$requests = [
[
'method' => 'GET',
'relative_url' => '/b',
'options' => 'a=b&cd',
],
[
'method' => 'GET',
'relative_url' => '/c',
'options' => 'a=b&cd',
],
];
$kernel = $this->get('http_kernel');
$responses = [];
foreach($requests as $aRequest){
// Construct a query params. Is only an example i don't know your input
$options=[];
parse_str($aRequest['options'], $options);
// Construct a new request object for each batch request
$req = Request::create(
$aRequest['relative_url'],
$aRequest['method'],
$options
);
// process the request
// TODO handle exception
$response = $kernel->handle($req);
$responses[] = [
'method' => $aRequest['method'],
'url' => $aRequest['relative_url'],
'code' => $response->getStatusCode(),
'headers' => $response->headers,
'body' => $response->getContent()
];
}
return new JsonResponse($responses);
}
With the following controller method:
/**
* #Route("/a", name="route_a_")
*/
public function aAction(Request $request)
{
return new Response('A');
}
/**
* #Route("/b", name="route_b_")
*/
public function bAction(Request $request)
{
return new Response('B');
}
/**
* #Route("/c", name="route_c_")
*/
public function cAction(Request $request)
{
return new Response('C');
}
The output of the request will be:
[
{"method":"GET","url":"\/b","code":200,"headers":{},"body":"B"},
{"method":"GET","url":"\/c","code":200,"headers":{},"body":"C"}
]
PS: I hope that I have correctly understand what you need.
There are ways to optimise test-speed, both with PHPunit configuration (for example, xdebug config, or running the tests with the phpdbg SAPI instead of including the Xdebug module into the usual PHP instance).
Because the code will always be running the AppKernel class, you can also put some optimisations in there for specific environments - including initiali[zs]ing the container less often during a test.
I'm using one such example by Kris Wallsmith. Here is his sample code.
class AppKernel extends Kernel
{
// ... registerBundles() etc
// In dev & test, you can also set the cache/log directories
// with getCacheDir() & getLogDir() to a ramdrive (/tmpfs).
// particularly useful when running in VirtualBox
protected function initializeContainer()
{
static $first = true;
if ('test' !== $this->getEnvironment()) {
parent::initializeContainer();
return;
}
$debug = $this->debug;
if (!$first) {
// disable debug mode on all but the first initialization
$this->debug = false;
}
// will not work with --process-isolation
$first = false;
try {
parent::initializeContainer();
} catch (\Exception $e) {
$this->debug = $debug;
throw $e;
}
$this->debug = $debug;
}

Resources