MercadoLibre API gives error "getAuthUrl() should not be called statically" - php

I have the following 2 codes from library https://github.com/javiertelioz/mercadolibre to connect with MercadoLibre's API:
Class Meli.php:
<?php
namespace App\Sources;
use App\Sources\MercadoLibre\Utils;
class Meli extends Utils {
/**
* #version 1.0.0
*/
const VERSION = "1.0.0";
/**
* Configuration for urls
*/
protected $urls = array(
'API_ROOT_URL' => 'https://api.mercadolibre.com',
'AUTH_URL' => 'http://auth.mercadolibre.com.ar/authorization',
'OAUTH_URL' => '/oauth/token'
);
/**
* Configuration for CURL
*/
protected $curl_opts = array(
CURLOPT_USERAGENT => "MELI-PHP-SDK-1.0.0",
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 60
);
protected $client_id;
protected $client_secret;
/**
* Constructor method. Set all variables to connect in Meli
*
* #param string $client_id
* #param string $client_secret
* #param string $access_token
*/
public function __construct($client_id, $client_secret, $urls = null, $curl_opts = null) {
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->urls = $urls ? $urls : $this->urls;
$this->curl_opts = $curl_opts ? $curl_opts : $this->curl_opts;
}
/**
* Return an string with a complete Meli login url.
*
* #param string $redirect_uri
* #return string
*/
public function getAuthUrl($redirect_uri) {
$params = array("client_id" => $this->client_id, "response_type" => "code", "redirect_uri" => $redirect_uri);
$auth_uri = $this->urls['AUTH_URL'] . "?" . http_build_query($params);
return $auth_uri;
}
}
and the Controller MeliController.php with the following code:
class MeliController extends Controller
{
/**
* Login Page (Mercado Libre)
*/
public function login() {
session()->regenerate();
return view('auth/melilogin')->with('auth', [
'url' => meli::getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
]);
}
public function logout() {
if(session('profile')) {
session()->forget('profile');
session()->flush();
}
return \Redirect::to('/auth/melilogin');
}
}
But Im receiving error:
Non-static method App\Sources\Meli::getAuthUrl() should not be called
statically
Three procedures I made with no success:
1- using facade (meli) as in the first example
meli::getAuthUrl
2- replacing code:
public function getAuthUrl($redirect_uri) {
$params = array("client_id" => $this->client_id, "response_type" => "code", "redirect_uri" => $redirect_uri);
$auth_uri = $this->urls['AUTH_URL'] . "?" . http_build_query($params);
return $auth_uri;
}
}
with public static function and $self instead of $this but with no success.
3- Making the call dynamic using:
'url' => (new \App\Sources\Meli)->getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
But receiving error
Too few arguments to function App\Sources\Meli::__construct(), 0
passed in
/Applications/MAMP/htdocs/price2b/app/Http/Controllers/MeliController.php
any help appreciated.

The error tells you the problem: you are calling the method statically (meli::getAuthUrl(...)), but it's not a static method. You have to call it on an instance of the class. This means that your third approach:
'url' => (new \App\Sources\Meli)->getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
is the right one.
But, as you pointed out, you get a "too few arguments" error. This is because you are passing no arguments when you instantiate the Meli class. That is, new \App\Sources\Meli is equivalent to new \App\Sources\Meli(), passing zero arguments to the constructor.
But the constructor for the Meli class, which you posted above, looks like this:
public function __construct($client_id, $client_secret, $urls = null, $curl_opts = null)
So, you need to pass at least 2 arguments, not zero. In other words, at a minimum, something like this:
'url' => (new \App\Sources\Meli($someClientId, $someClientSecret))->getAuthUrl(env('ML_AUTHENTICATION_URL', '')),

Related

laravel write proper test for sending email

I wonder how to write proper unit test for my email sending method. It's a problem because inside method I get data from Auth object. Should I send id of user in Request?
public function sendGroupInvite(Request $request){
foreach ($request->get('data') as $item){
$invitations = new \App\Models\Invitations();
$invitations->user_id = Auth::id();
$invitations->name = $item["name"];
$invitations->email = $item["email"];
$invitations->status = 0;
$invitations->token = \UUID::getToken(20);
$invitations->username = Auth::user()->name;
$invitations->save();
$settings = UserSettings::where('user_id', Auth::id())->first();
$email = $item["email"];
$url = 'https://example.com/invite/accept/'.$invitations->token;
$urlreject = 'https://example.com/invite/reject/'.$invitations->token;
$mailproperties = ['token' => $invitations->token,
'name' => $invitations->name,
'url' => $url,
'email' => $email,
'urlreject' => $urlreject,
'userid' => Auth::id(),
'username' => Auth::user()->name,
'user_name' => $settings->name,
'user_lastname' => $settings->lastname,
'user_link' => $settings->user_link,
];
$this->dispatch(new SendMail(new Invitations($mailproperties)));
}
return json_encode(array('msg' => 'ok'));
}
I'm using Auth to get username and user id. When I testing it it's not works, because Auth it's null.
I would go with mocking the queue, something similar to this. Mock Documentation
class MailTester extends TestCase{
/**
* #test
*/
public function test_mail(){
Queue::fake();
// call your api or method
Queue::assertPushed(SendMail, function(SendMail $job) {
return $job->something = $yourProperties;
});
}
You could try "acting as" to deal with the Auth::user().
...
class MyControllerTest extends TestCase{
/**
* #test
*/
public function foo(){
$user = App\Users::find(env('TEST_USER_ID')); //from phpunit.xml
$route = route('foo-route');
$post = ['foo' => 'bar'];
$this->actingAs($user)//a second param is opitonal here for api
->post($route, $post)
->assertStatus(200);
}
}

Yii2 $_GET and Yii::$app->request->get() not working

Background
I'm having an odd issue with a simple line of code that I don't understand. I have an Action on a Controller that i'm using for LinkedIn auth. The first time the user hits the controller it redirects to the LinkedIn site for authentication, once the user authenticates linked in redirects back to the same controller with the auth code in the url as a param.
http://beta.consynki.com/authentication/network/linkedin?code=DQTdGfxIlbsU...
Controller
class AuthenticationController extends WebController {
public function actionNetwork($network){
$access_token = Yii::$app->request->get('code');
$network_connection = NetworkFactory::build($network);
$client = $network_connection->getClient();
if($access_token && !is_null($access_token)){
$headers = Yii::$app->response->headers;
$headers->set('Pragma', 'no-cache');
$headers->add('X-Access-Token', $access_token);
return $this->render('success');
}
return $this->redirect($client->getLoginUrl(),302)->send();
}
}
EDIT 1 - WebController
/**
* Class WebController
*
* Default controller for public web pages. This class pulls meta tags from a seporately stored file, and makes
* them available to the view.
*
* #package www\components
*/
class WebController extends Controller {
public $meta = [];
public function beforeAction($event) {
$controller = $this->id;
$action = $this->action->id;
$meta_file = Yii::getAlias('#www') . '/meta/' . $controller . '/' . $action . '.php';
if (file_exists($meta_file)) {
$this->meta = include($meta_file);
$this->setMetaTags($this->meta);
}
return parent::beforeAction($event);
}
/**
* Set the meta tags for a page
*
* #param string $type
* #param $tag
* #param $value
*/
public function registerMetaTag($type = 'name', $tag, $value) {
if (!is_null($value)) {
$this->view->registerMetaTag([
$type => $tag,
'content' => $value
]);
}
}
public function behaviors() {
return [
/**
* The particular campaign used.
*
* Example social_share, stay_connected_add
*/
'utm_campaign' => [
'class' => 'common\behavior\TrackingBehavior',
'queryParam' => 'utm_campaign',
'sessionParam' => 'utm_campaign',
],
/*
* The source of the referral, could be an add network, facebook, email or just a link on the Consynki site.
*
* example: google, facebook, citysearch, welcome_email
*/
'utm_source' => [
'class' => 'common\behavior\TrackingBehavior',
'queryParam' => 'utm_source',
'sessionParam' => 'utm_source',
],
];
}
protected function setMetaTags($meta) {
foreach ($meta AS $key => $value) {
if (is_array($value)) {
$this->view->registerMetaTag($value, $key);
}
}
}
}
Problem
When I try to get the code from the GET param Yii::$app->request->get('code'); I get a NULL value. On further inspection of the $_GET array var_dump($app->request->get() or var_dump($_GET); I see the key for the code variable has a $ "?code" in front of it. This is very odd.
array(3) { ["network"]=> string(8) "linkedin" ["?code"]=> string(115) "DQTdGfxIlbsU..." ["state"]=> string(32) "12121..." }
Research Notes
It looks like Yii2 modify's the $_GET value as it passes the url routing. Not sure if this is the issue. Have updated to the latest version of yii and it didn't fix the problem.
Question
Why would this happen? How can I fix it so that I can get the code value?
Set rules there like this:
'authentication/network/<network:\w+>/<code:\w+>' => 'authentication/network',
'authentication/network/<network:\w+>' => 'authentication/network',
Now in action set parameters like:
public function actionNetwork($network, $code = null)
Your previous $access_token is now $code.

Laravel testing with JWT-Auth

I'm trying to test my api that's made with JWT_auth: https://github.com/tymondesigns/jwt-auth
class UpdateTest extends TestCase
{
use DatabaseTransactions;
public $token;
public function signIn($data = ['email'=>'mail#gmail.com', 'password'=>'secret'])
{
$this->post('api/login', $data);
$content = json_decode($this->response->getContent());
$this->assertObjectHasAttribute('token', $content);
$this->token = $content->token;
return $this;
}
/** #test */
public function a_user_updates_his_account()
{
factory(User::class)->create([
'name' => 'Test',
'last_name' => 'Test',
'email' => 'mail#gmail.com',
'mobile' => '062348383',
'function' => 'ceo',
'about' => 'About me.....',
'corporation_id' => 1
]);
$user = User::first();
$user->active = 2;
$user->save();
$this->signIn();
$url = '/api/user/' . $user->slug . '?token=' . $this->token;
$result = $this->json('GET', $url);
dd($result);
}
}
Result is always:
The token could not be parsed from the request
How do I get this t work!?
Source (https://github.com/tymondesigns/jwt-auth/issues/206)
One way to test your API in this situation is to bypass the actual token verification, but still log your user in (if you need to identify the user). Here is a snippet of the helper method we used in our recent API-based application.
/**
* Simulate call api
*
* #param string $endpoint
* #param array $params
* #param string $asUser
*
* #return mixed
*/
protected function callApi($endpoint, $params = [], $asUser = 'user#example.org')
{
$endpoint = starts_with($endpoint, '/')
? $endpoint
: '/' . $endpoint;
$headers = [];
if (!is_null($asUser)) {
$token = auth()->guard('api')
->login(\Models\User::whereEmail($asUser)->first());
$headers['Authorization'] = 'Bearer ' . $token;
}
return $this->json(
'POST',
'http://api.dev/' . $endpoint,
$params,
$headers
);
}
And is used like this:
$this->callApi('orders/list', [
'type' => 'customers'
])
->seeStatusOk()
Basically, there is not really a way for now. The fake request that is created during testing and is passed to Laravel to handle, somehow drops the token data.
It has alredy been reported in an issue (https://github.com/tymondesigns/jwt-auth/issues/852) but as far as I know, there is no solution yet.

Android push (GCM) from App Engine using Zend Framework

I'm trying to implement GCM server using PHP and Zend Framework on Google App Engine. So far it works fine locally, but fails with this message when uploaded to App Engine:
Here is the code:
$ids = '....my device id....';
$apiKey = '...my api key...';
$data = array( 'message' => 'Hello World!' );
$gcm_client = new Client();
$gcm_client->setApiKey($apiKey);
$gcm_message = new Message();
$gcm_message->setTimeToLive(86400);
$gcm_message->setData($data);
$gcm_message->setRegistrationIds($ids);
$response = $gcm_client->send($gcm_message);
var_dump($response);
And it fails with this error message:
PHP Fatal error: Uncaught exception 'ErrorException' with message
'stream_socket_client(): unable to connect to
android.googleapis.com:443 (Unknown error 4294967295)' in
/base/data/home/..../backend:v1.375711862873219029/vendor/zendframework/zend-http/Zend/Http/Client/Adapter/Socket.php:253
I know App Engine doesn't allow socket connections and offers urlFetch wrapper for http and https, but how do I tell Zend Framework to use this transport?
Try enabling Billing. As far as I remember sockets are enabled only for paid apps.
This won't charge you anything (unless you exceed free quota) but should get rid of the error.
Promoted this from a comment - I ended up making my own class implementing Zend\Http\Client\Adapter\AdapterInterface that uses URLFetch by opening a URL using the usual fopen with stream context to send POST request. Although this works I'm not sure it's the best way. Would prefer to use the framework capabilities, if possible.
I'm not sure if this is going to help anyone, as both ZendFramework and AppEngine have evolved since I asked the question, but here is the adapter I've implemented:
use Zend\Http\Client\Adapter\AdapterInterface;
use Zend\Http\Client\Adapter\Exception\RuntimeException;
use Zend\Http\Client\Adapter\Exception\TimeoutException;
use Zend\Stdlib\ErrorHandler;
class URLFetchHttpAdapter implements AdapterInterface
{
protected $stream;
protected $options;
/**
* Set the configuration array for the adapter
*
* #param array $options
*/
public function setOptions($options = array())
{
$this->options = $options;
}
/**
* Connect to the remote server
*
* #param string $host
* #param int $port
* #param bool $secure
*/
public function connect($host, $port = 80, $secure = false)
{
// no connection yet - it's performed in "write" method
}
/**
* Send request to the remote server
*
* #param string $method
* #param \Zend\Uri\Uri $url
* #param string $httpVer
* #param array $headers
* #param string $body
*
* #throws \Zend\Loader\Exception\RuntimeException
* #return string Request as text
*/
public function write($method, $url, $httpVer = '1.1', $headers = array(), $body = '')
{
$headers_str = '';
foreach ($headers as $k => $v) {
if (is_string($k))
$v = ucfirst($k) . ": $v";
$headers_str .= "$v\r\n";
}
if (!is_array($this->options))
$this->options = array();
$context_arr = array("http" =>
array( "method" => $method,
"content" => $body,
"header" => $headers_str,
"protocol_version" => $httpVer,
'ignore_errors' => true,
'follow_location' => false,
) + $this->options
);
$context = stream_context_create($context_arr);
ErrorHandler::start();
$this->stream = fopen((string)$url, 'r', null, $context);
$error = ErrorHandler::stop();
if (!$this->stream) {
throw new \Zend\Loader\Exception\RuntimeException('', 0, $error);
}
}
/**
* Read response from server
*
* #throws \Zend\Http\Client\Adapter\Exception\RuntimeException
* #return string
*/
public function read()
{
if ($this->stream) {
ErrorHandler::start();
$metadata = stream_get_meta_data($this->stream);
$headers = join("\r\n", $metadata['wrapper_data']);
$contents = stream_get_contents($this->stream);
$error = ErrorHandler::stop();
if ($error)
throw $error;
$this->close();
//echo $headers."\r\n\r\n".$contents;
return $headers."\r\n\r\n".$contents;
} else {
throw new RuntimeException("No connection exists");
}
}
/**
* Close the connection to the server
*
*/
public function close()
{
if (is_resource($this->stream)) {
ErrorHandler::start();
fclose($this->stream);
ErrorHandler::stop();
$this->stream = null;
}
}
/**
* Check if the socket has timed out - if so close connection and throw
* an exception
*
* #throws TimeoutException with READ_TIMEOUT code
*/
protected function _checkSocketReadTimeout()
{
if ($this->stream) {
$info = stream_get_meta_data($this->stream);
$timedout = $info['timed_out'];
if ($timedout) {
$this->close();
throw new TimeoutException(
"Read timed out after {$this->options['timeout']} seconds",
TimeoutException::READ_TIMEOUT
);
}
}
}
}
public function sendAndroidPushNotification($registration_ids, $message)
{
$registrationIds = array($registration_ids);
$msg = array(
'message' => $message,
'title' => 'notification center',
'vibrate' => 1,
'sound' => 1
);
$fields = array(
'registration_ids' => $registrationIds,
'data' => $msg
);
$fields = json_encode($fields);
$arrContextOptions=array(
"http" => array(
"method" => "POST",
"header" =>
"Authorization: key = <YOUR_APP_KEY>". "\r\n" .
"Content-Type: application/json". "\r\n",
"content" => $fields,
),
"ssl"=>array(
"allow_self_signed"=>true,
"verify_peer"=>false,
),
);
$arrContextOptions = stream_context_create($arrContextOptions);
$result = file_get_contents('https://android.googleapis.com/gcm/send', false, $arrContextOptions);
return $result;
}

Laravel - input not passing over through unit test

I'm receiving the below error when running my unit tests. Seems that it doesn't like passing in the Input::get to the constructor, however when running the script within the browser the action works fine so I know it's not the controller code. If I take out any of the 'task_update' code the test passes with just the find even with the Input - so not sure why it accepts the Input for one method.
ErrorException: Argument 1 passed to Illuminate\Database\Eloquent\Model::__construct() must be of the type array, null given, called
My Controller is:
public function store()
{
$task_update = new TaskUpdate(Input::get('tasks_updates'));
$task = $this->task->find(Input::get('tasks_updates')['task_id']);
$output = $task->taskUpdate()->save($task_update);
if (!!$output->id) {
return Redirect::route('tasks.show', $output->task_id)
->with('flash_task_update', 'Task has been updated');
}
}
And the test is - I'm setting the input for task_updates array but just isn't being picked up:
Input::replace(['tasks_updates' => array('description' => 'Hello')]);
$mockClass = $this->mock;
$mockClass->task_id = 1;
$this->mock->shouldReceive('save')
->once()
->andReturn($mockClass);
$response = $this->call('POST', 'tasksUpdates');
$this->assertRedirectedToRoute('tasks.show', 1);
$this->assertSessionHas('flash_task_update');
I believe the "call" function is blowing away the work done by Input::replace.
the call function can actually take a $parameters parameter which should fix your problem.
if you look in \Illuminate\Foundation\Testing\TestCase#call, you'll see the function:
/**
* Call the given URI and return the Response.
*
* #param string $method
* #param string $uri
* #param array $parameters
* #param array $files
* #param array $server
* #param string $content
* #param bool $changeHistory
* #return \Illuminate\Http\Response
*/
public function call()
{
call_user_func_array(array($this->client, 'request'), func_get_args());
return $this->client->getResponse();
}
If you do:
$response = $this->call('POST', 'tasksUpdates', array('your data here'));
I think it should work.
I do prefer to do both Input::replace($input) and $this->call('POST', 'path', $input).
Example AuthControllerTest.php:
public function testStoreSuccess()
{
$input = array(
'email' => 'email#gmail.com',
'password' => 'password',
'remember' => true
);
// Input::replace($input) can be used for testing any method which
// directly gets the parameters from Input class
Input::replace($input);
// Here the Auth::attempt gets the parameters from Input class
Auth::shouldReceive('attempt')
->with(
array(
'email' => Input::get('email'),
'password' => Input::get('password')
),
Input::get('remember'))
->once()
->andReturn(true);
// guarantee we have passed $input data via call this route
$response = $this->call('POST', 'api/v1/login', $input);
$content = $response->getContent();
$data = json_decode($response->getContent());
//... assertions
}

Categories