CakePHP 3.6 Controller Integration Testing - HTTP requests not sent - testing

I am trying to implement controller integration testing in CakePHP 3.6 using its testing tools. I assumed that this would be handled by making a 'real' (as in CURL) HTTP request against the running webserver, but it looks like it isn't. Below is the test case code I'm using.
The problems I'm running into:
The test case is somehow managing to access the controler action,
even when the webserver is not running at all (Apache down and no
dev webserver running).
When running this test, the controller does not have access to
$_SERVER (see below) and any of the $postData defined in the test case appears empty on the controller side.
When I place exit; in the controller code, the whole test case
stops, which suggests that the controller code is run directly, not
via a HTTP request.
Question: How can I make a 'real' HTTP requests when testing controllers, apart from resorting to using CURL and handling the requests manually?
Clearly, I am either not understanding how the controller testing is done, or I'm doing something wrong.
Test case I'm using:
/tests/TestCase/Controller/JobsControllerTest.php
<?php
namespace App\Test\TestCase\Controller;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\IntegrationTestCase;
/**
* App\Controller\JobsController Test Case
*/
class JobsControllerTest extends IntegrationTestCase
{
/**
* Test add method
*
* #return void
*/
public function testAdd()
{
$this->useHttpServer(true);
$this->configRequest([
'headers' => [
'Content-Type' => 'application/json',
'X-Api-Key' => '8f083c8f083c8f083c8f083c'
]
]);
$postData = [
'user_id' => 3,
'job_status' => 'New'
];
$this->post('/jobs/add', $postData);
$this->assertResponseSuccess();
$jobs = TableRegistry::get('Jobs');
$query = $jobs->find()->where(['user_id' => $postData['user_id']]);
$this->assertEquals(1, $query->count());
}
}
The dump of $_SERVER global from the controller that I'm testing:
Array
(
[LS_COLORS] => rs=0:di=01;34 [...]
[LANG] => en_US.UTF-8
[HOME] => /home/tomasz
[TERM] => screen
[PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
[MAIL] => /var/mail/root
[LOGNAME] => root
[USER] => root
[USERNAME] => root
[SHELL] => /bin/bash
[SUDO_COMMAND] => vendor/bin/phpunit --verbose
[SUDO_USER] => tomasz
[SUDO_UID] => 1000
[SUDO_GID] => 1000
[PHP_SELF] => vendor/bin/phpunit
[SCRIPT_NAME] => vendor/bin/phpunit
[SCRIPT_FILENAME] => vendor/bin/phpunit
[PATH_TRANSLATED] => vendor/bin/phpunit
[DOCUMENT_ROOT] =>
[REQUEST_TIME_FLOAT] => 1546631688.0758
[REQUEST_TIME] => 1546631688
[argv] => Array
(
[0] => vendor/bin/phpunit
[1] => --verbose
)
[argc] => 2
)

CakePHP integration tests do not issue actual HTTP requests, they simulate them, it's very fast, allows for certain mocking, inspecting session contents, accessing exception details, etc., all sorts of things that wouldn't really be possible (at least not easily) when using real HTTP requests. If you really need to issue actual requests, then you should look into using other utilities, like for example Codeception (specifically acceptance tests).
When using CakePHP, it is advised that you do not access PHP superglobals directly, but that you retrieve the data from the abstracted APIs provided by CakePHP! Breaking your integration tests is one of the reasons for this. The simulated request will receive a request object that has been prepared with the data from your test case, that is where you need to look it up.
For example if you want to access POST data in your app, maybe in your controller, then you do it like this:
$user_id = $this->request->getData('user_id');
See also
Cookbook > Testing > Controller Integration Testing
Cookbook > Request & Response Objects

Related

"Attempt to read property \"id\" on null", Create access token error with laravel passport, laravel 9, php 8.2.2

devs,
so I have been struggling with this problem for about 10 hours now, and I can't seem to find a solution online, worst is that I don't even know why it happens.
I am working on a project which uses PHP LARAVEL as the backend and I started writing the API for the flutter frontend to consume then I ran into this error while trying to test the API endpoint for registering and logging in.
The problem is the process fails with this error when I try to generate or create a token for the registered user or logged-in user.
Here a snapshot of my register function
public function store(Request $request)
{
$validated = Validator::make($request->all(),[
"email" => "required|email",
"password" => 'required',
"first_name"=> "required",
"last_name" => "required",
"phone_number" => 'required',
]);
if ($validated->fails()) {
return response()->json(['errors' => "Invalide credentials"], 403);
}
$user = User::create(
// [
// 'first_name' => $request->first_name,
// 'last_name'=> $request->last_name,
// 'email' => $request->email,
// 'password' => bcrypt($request->password),
// 'phone_number' => $request->phone_number,
// ]
$request->toArray()
);
Auth::guard('api')->check($user);
// $newUser = User::find($user->id);
$token = $user->createToken('authToken')->accessToken;
// return $token;
return response(['token' => $token, 'first_name'=>$user->first_name, 'email'=>$user->email ], 200);
}
The login and register functions all look the same at this point.
Error-causing code is :
$token = $user->createToken('authToken')->accessToken;
Please I am open to your suggestions, thanks.
I finally found a solution for this error and I believe it will help anyone out there with a similar problem.
The problem originates from the fact that your application is unable to asign a unique id to your client, remember your website or mobile app is a client to the backend with also(your mobile app or website) might have other users, so laravel passport will need to identify it with a unique id, below are some of the steps i used to fix this error.
First it originates because during the passport installation, i forgot to install
Blockquote
--uuids
If you have a similar error, follow the steps below to fix:
NOTE: You must have laravel passport installed already, if not, them follow the complete installtion guide Here
Step 1:
Install passport uuids
php artisan passport:install --uuids
Your result will look something like
After creating, the uuid for your application, you will have to include it in your .env file as such:
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=986eb40c-0458-4b6e-bead-ea2fc4987033
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=VXLdTpqWK9i3CBqFwZgje5fuerQ5Uf2lvwXJqBoP
And there you go, you can now try to do what you couldn't do before.

Web push notification example with PHP backend

I am looking for an example for web push notification with JS code and PHP backend. Can anyone share example code or a tutorial?
Here's a basic example that uses web-push-php : https://github.com/Minishlink/web-push-php-example
Main PHP code is:
<?php
require __DIR__ . '/vendor/autoload.php';
use Minishlink\WebPush\WebPush;
$auth = array(
'VAPID' => array(
'subject' => 'https://github.com/Minishlink/web-push-php-example/',
'publicKey' => 'BCmti7ScwxxVAlB7WAyxoOXtV7J8vVCXwEDIFXjKvD-ma-yJx_eHJLdADyyzzTKRGb395bSAtxlh4wuDycO3Ih4',
'privateKey' => 'HJweeF64L35gw5YLECa-K7hwp3LLfcKtpdRNK8C_fPQ', // in the real world, this would be in a secret file
),
);
$webPush = new WebPush($auth);
$res = $webPush->sendNotification(
$subscription['endpoint'],
"Hello!", // payload
$subscription['key'],
$subscription['token'],
true // flush
);
// handle eventual errors here, and remove the subscription from your server if it is expired
Hope this helps :)

Testing a CakePHP 3 REST API

I am developing an API on CakePHP 3 using the CRUD Plugin and ADmad's JWT plugin. I've created fixtures for all tables by importing the schema and then defining some dummy records. Now I'd like to know the best way to test my API.
Namely:
How do I set an Authorized user in my tests?
How do I call API methods in the test framework?
I don't have any code to show at the moment because I'm really not sure how to go about this in the correct way.
The one way I see to test API endpoints is by using post() method of the IntegrationTestCase suite. A very basic example:
public function testLogin()
{
// You can add this to the setUp() function to make it global
$this->configRequest([
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'x-www-form-urlencoded'
]
]);
$this->post('/auth/token', ['username' => $username, 'password' => $password]);
$this->assertResponseOk();
$this->assertResponseContains('access_token');
}
Store that access token (or pre-generate one) & use that to set authorization header.
You can send your authorization tokens like so (before EVERY request):
$this->configRequest([
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'x-www-form-urlencoded',
'Authorization' => 'Bearer ' . $token
]
]);
TIP: You can Configure::write() the Security.salt values in bootstrap.php of the test - this way the password salting works! I also found saving the salted password value in your fixture helpful.
More details in CakePHP Cookbook.

Paypal REST API invalid credentials

I use the REST api in my nodejs application.
All is working good with sandbox but when i update with live credentials i get:
{ [Error: Response Status : 401]
response:
{ error: 'invalid_client',
error_description: 'The client credentials are invalid',
httpStatusCode: 401 },
httpStatusCode: 401 }
I updated my account to buisness but still not working, i use the live endpoint and Live credentials.
What should i do in order to make this work?
I had the same issue using PayPalSDK/rest-sdk-nodejs and solved passing with the configuration parameters (host, client_id, client_secret, ...) also the parameter 'mode' set to 'live'. Otherwise the default mode used by the library is 'sandbox' and hence the impossibility to use the live credentials.
As matteo said, if you switch from dev to live environment, only updateing the client id and secret isn't enough. You need to set the ApiContext-Mode to "live".
PayPals PHP REST-API-SDK comes with some great samples. Take a look at the bootstrap.php in /vendor/paypal/rest-api-sdk-php/sample/ in line 84. There are some configurations happening, after getting the api context.
<?php
$apiContext = new ApiContext(
new OAuthTokenCredential(
$clientId,
$clientSecret
)
);
// Comment this line out and uncomment the PP_CONFIG_PATH
// 'define' block if you want to use static file
// based configuration
$apiContext->setConfig(
array(
'mode' => 'sandbox',
'log.LogEnabled' => true,
'log.FileName' => '../PayPal.log',
'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
'cache.enabled' => true,
// 'http.CURLOPT_CONNECTTIMEOUT' => 30
// 'http.headers.PayPal-Partner-Attribution-Id' => '123123123'
//'log.AdapterFactory' => '\PayPal\Log\DefaultLogFactory' // Factory class implementing \PayPal\Log\PayPalLogFactory
)
);

RavenDB Query: Have to use Customize() instead of Include()

I'm getting an error that I'm exceeding the number of requests allowed per session (30) when using this query (using Include instead of Customize):
ApplicationServer appServer = QuerySingleResultAndSetEtag(session => session
.Include<ApplicationServer>(x => x.CustomVariableGroupIds)
.Include<ApplicationServer>(x => x.ApplicationIdsForAllAppWithGroups)
.Include<ApplicationServer>(x => x.CustomVariableGroupIdsForAllAppWithGroups)
.Include<ApplicationServer>(x => x.CustomVariableGroupIdsForGroupsWithinApps)
.Include<ApplicationServer>(x => x.InstallationEnvironmentId)
.Load <ApplicationServer>(id))
as ApplicationServer;
Note, the error occurs on this line, which is called for each AppWithGroup within an application:
appGroup.Application = QuerySingleResultAndSetEtag(session =>
session.Load<Application>(appGroup.ApplicationId)) as Application;
However, this query (using Customize) doesn't create extra requests:
ApplicationServer appServer = QuerySingleResultAndSetEtag(session =>
session.Query<ApplicationServer>()
.Customize(x => x.Include<ApplicationServer>(y => y.CustomVariableGroupIds))
.Customize(x => x.Include<ApplicationServer>(y => y.ApplicationIdsForAllAppWithGroups))
.Customize(x => x.Include<ApplicationServer>(y => y.CustomVariableGroupIdsForAllAppWithGroups))
.Customize(x => x.Include<ApplicationServer>(y => y.CustomVariableGroupIdsForGroupsWithinApps))
.Customize(x => x.Include<ApplicationServer>(y => y.InstallationEnvironmentId))
.Where(server => server.Id == id).FirstOrDefault())
as ApplicationServer;
However, the above query causes an error:
Attempt to query by id only is blocked, you should use call
session.Load("applications/2017"); instead of
session.Query().Where(x=>x.Id == "applications/2017");
You can turn this error off by specifying
documentStore.Conventions.AllowQueriesOnId = true;, but that is not
recommend and provided for backward compatibility reasons only.
I had to set AllowQueriesOnId = true because it was the only way I could get this to work.
What am I doing wrong in the first query to cause the includes not to work?
By the way, another post had the same issue where he had to use Customize. I'd like to do this correctly though.
I'm not sure why the load isn't doing this for you, What version of raven are you on? I just tested this in Raven 2.5 build 2700 and the include is bringing back the information for me in a single request.
Anyway, with the load not working quite like i would expect, i would switch to a set of lazy queries to get what you want in 2 server round trips. http://ravendb.net/docs/2.5/client-api/querying/lazy-operations.
Another option that might work out better for you, (depending on what you are really doing with all of that data) is a transformer. http://ravendb.net/docs/2.5/client-api/querying/results-transformation/result-transformers?version=2.5
Hope that helps.