Mocking external library in PHP/Codeigniter - php

Using the ci-php-unit-test library to do unit tests for PHP/codeigniter, and unit testing controller methods.
Having trouble working out how to mock an external library that is installed using composer.
my SUT method is:
function twitter()
{
$this->load->model('misc/twitter_model');
$request_token = [];
$request_token['oauth_token'] = $_SESSION['twitter_oauth_token'];
$request_token['oauth_token_secret'] = $_SESSION['twitter_oauth_token_secret'];
if ( (isset($_GET['oauth_token'])
&& ($request_token['oauth_token'] !== $_GET['oauth_token'])))
{
log_message('info','abort something is wrong!');
}
else
{
$connection = new Abraham\TwitterOAuth\TwitterOAuth(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET, $request_token['oauth_token'], $request_token['oauth_token_secret']);
$access_token = $connection->oauth("oauth/access_token", array("oauth_verifier" => $_REQUEST['oauth_verifier']));
$this->session->set_userdata('twitter_access_token',$access_token);
redirect(get_session('twitter_callback1'));
}
}
my test method (so far) is:
public function test_twitter()
{
$_SESSION['twitter_oauth_token'] = 'twitter_oauth_token';
$_SESSION['twitter_oauth_token_secret'] = 'twitter_oauth_token_secret';
$this->request->setCallable(
function (& $CI) {
// Get mock object
$twitter_oa = $this->getMockBuilder('TwitterOAuth')
->disableOriginalConstructor()
->setMethods(['oauth'])
->getMock();
$twitter_oa->method('oauth')
->willReturn('access_token');
}
);
$output = $this->request('GET','callbacks/twitter',['oauth_token'=>'twitter_oauth_token']);
var_export($output);
}
But the original library is being executed because it isn't being mocked - the $twitter_oa isn't being attached to the CI instance.
This is because the external library has not been instantiated after the codeigniter controller has been instantiated. (this is was the setCallable method does)
My question is, how can I mock TwitterOAuth after the codeigniter controller is instantiated so it can return set testing text?
(and obviously not instantiate the twitter Oauth library)

ok, so when you see a new Class(); in unit testing, you have to re-work something. This may not be the best option, but this works for me.
in the SUT, the controller code is now:
function twitter()
{
$this->load->model('misc/twitter_model');
$request_token = [];
$request_token['oauth_token'] = $_SESSION['twitter_oauth_token'];
$request_token['oauth_token_secret'] = $_SESSION['twitter_oauth_token_secret'];
if ( (isset($_GET['oauth_token'])
&& ($request_token['oauth_token'] !== $_GET['oauth_token'])))
{
log_message('info','abort something is wrong!');
}
else
{
$connection = $this->twitter_connection($request_token['oauth_token'],$request_token['oauth_token_secret']);
$access_token = $connection->oauth("oauth/access_token", array("oauth_verifier" => $_REQUEST['oauth_verifier']));
$this->session->set_userdata('twitter_access_token',$access_token);
redirect(get_session('twitter_callback1'));
}
}
/**
* #param $token
* #param $secret
* #return \Abraham\TwitterOAuth\TwitterOAuth
*
* #codeCoverageIgnore
*
*/
public function twitter_connection($token, $secret)
{
return new Abraham\TwitterOAuth\TwitterOAuth(
TWITTER_CONSUMER_KEY,
TWITTER_CONSUMER_SECRET,
$token,
$secret);
}
There is a new file in APPPATH.tests/mocks/external_libaries with:
class MockTwitterOAuth
{
public function oauth()
{
return 'access_token';
}
}
and the test code is now:
public function test_twitter()
{
$_SESSION['twitter_oauth_token'] = 'twitter_oauth_token';
$_SESSION['twitter_oauth_token_secret'] = 'twitter_oauth_token_secret';
$_SESSION['twitter_callback1'] = 'cb1';
require_once(APPPATH.'tests/mocks/external_libraries/MockTwitterOauth.php');
$twitter_connection = new MockTwitterOAuth();
MonkeyPatch::patchMethod('Callbacks',[
'twitter_connection'=>$twitter_connection
]);
$output = $this->request('GET','callbacks/twitter',['oauth_token'=>'twitter_oauth_token']);
$this->assertNull($output);
$this->assertResponseCode(HTTP_FOUND);
$this->assertRedirect(base_url('cb1'));
$this->assertEquals('access_token',$_SESSION['twitter_access_token']);
}
The trick is to have twitter connection return a new TwitterOAuth class normally, but when the system is unit testing, return the MockTwitterOAuth class that only has one method instead. This can be accomplished by monkeypatching the controller code.
Hope this answer is useful for others, and if you haven't used it already the https://github.com/kenjis/ci-phpunit-test is pretty good, even though it is hard to get started. Purchasing the companion book is recommended!

Related

Dynamically-created Twilio Enqueue waitUrl results in 500 server error

I have a function in my Laravel application that generates TwiML for a holding queue. It seems that when I try to dynamically generate the value for the waitUrl attribute, I end up getting a 500 server error during runtime. Routes are properly established and I'm able to view the correct XML at the waitURL in the browser. However, the error persists.
If I create a static XML file with the same exact content, or use a TwiML Bin, it works like a charm.
Here are the relevant functions:
public function wait() {
return $this->generateWaitTwiml();
}
public function onHold($agentId) {
return $this->generateHoldQueueTwiml($agentId, '/phone/wait');
}
private function generateHoldQueueTwiml($agentId, $waitUrl = null) {
$queue = $agentId . '_hold';
if ($waitUrl === null){
$waitUrl = 'path_to_static.xml';
}
$queue = $agentId . '_hold';
$response = new Twiml();
$response->enqueue(
$queue,
['waitUrl' => $waitUrl]
);
return response($response)->header('Content-Type', 'application/xml');
}
private function generateWaitTwiml() {
$response = new Twiml();
$response
->play('http://path_to_my.mp3');
return response($response)->header('Content-Type', 'application/xml');
}
This was resolved by excluding the URIs from the CSRF verification (in VerifyCsrfToken.php):
class VerifyCsrfToken extends Middleware {
protected $except = [
'uri/',
'uri2/*',
];
}

Soap server not working in Laravel 5.2

I'm trying to create a soap server in laravel 5.2. This is my code:
Content of SoapController.php:
<?php namespace Giant\Http\Controllers;
class SoapController extends Controller {
public function __construct() {
parent::__construct();
ini_set('soap.wsdl_cache_enabled', 0);
ini_set('soap.wsdl_cache_ttl', 0);
ini_set('default_socket_timeout', 300);
ini_set('max_execution_time', 0);
}
public function server() {
$location = url('server'); // http://payment.dev/server
$namespace = $location;
$class = "\\Giant\\Http\\Controllers\\HelloWorld";
$wsdl = new \WSDL\WSDLCreator($class, $location);
$wsdl->setNamespace($namespace);
if (isset($_GET['wsdl'])) {
$wsdl->renderWSDL();
exit;
}
$wsdl->renderWSDLService();
$wsdlUrl = url('wsdl/server.wsdl');
$server = new \SoapServer(
url('server?wsdl'),
array(
'exceptions' => 1,
'trace' => 1,
)
);
$server->setClass($class);
$server->handle();
exit;
}
public function client() {
$wsdl = url('server?wsdl');
$client = new \SoapClient($wsdl);
try {
$res = $client->hello('world');
dd($res);
} catch (\Exception $ex) {
dd($ex);
}
}
}
class HelloWorld {
/**
* #WebMethod
* #desc Hello Web-Service
* #param string $name
* #return string $helloMessage
*/
public function hello($name) {
return "hello {$name}";
}
}
My wsdl file is: wsdl
And my routes:
Route::any('/server', 'SoapController#server');
Route::any('/client', 'SoapController#client');
And the result I get:
Internal Server Error
:(
I use piotrooo/wsdl-creator to generate wsdl. (There is no problem with that, It is working in laravel 4.2). And I have also tried nusoap and php2wsdl libraries.
My SoapClient is working well. Because it can get service from other soap servers in other urls, But I think my SoapServer can not work well.
I even get no errors in error-log file.
I just figured out wht was the problem:
The problem with log was that i was checking error-log in my www folder while laravel has its own log file. And using that i figured that i have problem with TokenMismatchException. Laravel's CsrfVerifyMiddleware would not letting me to request using soap.
I just added my url to "except" array inside CsrfVerifyMiddleware file.
Do not use two classes in one file
This is my experience from our project in which used Soap
This is SoapServerController . Paste wsdl file in root folder of your project
class SoapServerController extends Controller {
public function service() {
$server = new \SoapServer('http://' . request()->server('HTTP_HOST') . '/yourwsdlfile.wsdl');
$server->setClass('App\Http\Requests\somenamespace\SoapRequest');
$server->handle();
}
}
and in requests create class for requests like this:
class SoapRequest{
public function functionFromWsdl($args if you want) {
$parameters = (array) $args;
return with(new fooClass())->barMethod($parameters);
}
}
and route must be post:
Route::post('webservice','SoapServerController#service');
In laravel 5 all before statements have turned into middlewares (just like what is in django framework). And you need to implement using middlewares.

PHP Oauth 1.0 provider, signatures do not match

I am trying to setup an Oauth server using the pecl-php oauth library http://php.net/manual/en/book.oauth.php
This code assumes that the client has already received a user verified access token, for simplicity sake I've not included any database calls and have hardcoded matching values into my client and provider.
Class OauthVerify
{
private static $consumer_secret = 'f63ed7f7a8899e59d3848085c9668a0d';
private static $token_secret = '72814e6059441037152eecef2e8559a748b84259';
private $provider;
public function __construct()
{
$this->provider = new OAuthProvider();
$this->provider->consumerHandler(array($this,'consumerHandler'));
$this->provider->timestampNonceHandler(array($this,'timestampNonceHandler'));
$this->provider->tokenHandler(array($this,'checkAccessToken'));
}
//Check the client request
public function checkRequest()
{
try {
$this->provider->checkOAuthRequest();
} catch (Exception $Exception) {
return OAuthProvider::reportProblem($Exception);
}
return true;
}
public static function timestampNonceHandler($Provider)
{
//I'm leaving out this logic now, to keep it simple and for testing purposes
return OAUTH_OK;
}
public static function consumerHandler($Provider)
{
//I'm leaving out this logic now, to keep it simple and for testing purposes
$Provider->consumer_secret = self::$consumer_secret;
return OAUTH_OK;
}
public static function checkAccessToken($Provider)
{
$Provider->token_secret = self::$token_secret;
return OAUTH_OK;
}
}
The above code should give me the barebones I need to authenticate an Oauth request.
Before any particular route is executed I call the $OauthVerify->checkRequest() method which checks if the client request is valid, however the server keeps throwing a 'signatures do not match' error. I don't think that the problem is with the clients as I've tried both postman (for chrome) and a PHP implementation and they both generate the same signature. I have however for interest sake included my client call.
$consumer_key = '87d6d61e87f0e30d8747810ae40041d1';
$consumer_secret = 'f63ed7f7a8899e59d3848085c9668a0d';
$token= 'b9d55b3ec4b755d3fe25d7a781da1dfd044b5155';
$token_secret = '72814e6059441037152eecef2e8559a748b84259';
$timestamp = '1417515075';
$nonce = '9QV4rn';
$version = '1.0';
$method = 'GET';
$url = 'https://localhost/micro/v1/nappi';
try {
$oauth = new OAuth($consumer_key, $consumer_secret, OAUTH_SIG_METHOD_HMACSHA1, OAUTH_AUTH_TYPE_URI);
$oauth->enableDebug();
$oauth->disableSSLChecks();
$oauth->setNonce($nonce);
$oauth->setTimestamp($timestamp);
$oauth->setToken($token, $token_secret);
$oauth->setVersion($version);
$oauth->fetch("$url");
$json = json_decode($oauth->getLastResponse());
print_r($json);
}
catch(OAuthException $E) {
print_r($E);
}
I've burned a good couple of hours trying to figure this out, someone please help!
I finally managed to solve it, my .htaccess file had a rewrite rule that was processing a _url parameter for my framework. This parameter was ofcourse being included in the signature that the server generated. I simply instructed OAuth Provider to ignore the the _url parameter in my constructor:
$this->provider->setParam('_url',NULL);,
that was all it took, everything runs perfectly now.

PHP: Load a class in a class

I have a class which is meant to "load" an another class, however I haven't been able to get it to work.
Error Message
Fatal error: Call to undefined method stdClass::echoString() in C:\Program Files (x86)\EasyPHP-DevServer-14.1VC11\data\localweb\classes\example.php on line 5
Code
My code is broken up into three main sections:
api.php - the class to load the other classes.
API/exampleExternalAPI.php - (multiple files) the classes that api.php loads
example.php - the file that uses the main class (api.php)
If it helps these files can be downloaded from my dropbox
api.php
<?php
/* Config */
define('pathToAPIs','API/');
/* Autoload Function */
spl_autoload_register(function($className){
$namespace=str_replace("\\","/",__NAMESPACE__);
$className=str_replace("\\","/",$className);
$class=pathToAPIs.(empty($namespace)?"":$namespace."/")."{$className}.php";
include_once($class);
});
class api {
private $listOfAPIs;
public $APIs;
public function __construct($setAPI = null){
$this->updateListOfAPIs();
if (isset($setAPI)){
return $this->setAPI($setAPI);
}
}
public function setAPIs($setAPIs){
$this->APIs = null; // clears a previous call to this method
if (!is_array($setAPIs)){ // if not an array
$setAPIs = array($setAPIs); // make array
}
foreach ($setAPIs as $setAPIType){
if(in_array($setAPIType,$this->listOfAPIs)){
$array[$setAPIType] = new $setAPIType;
}
}
$this->APIs = json_decode(json_encode($array), FALSE); // convert array of required api objects to an object
return $this->APIs;
}
public function getListOfAPIs($update = false){
if ($update){
$this->updateListOfAPIs();
}
return $this->listOfAPIs;
}
private function updateListOfAPIs(){
$this->listOfAPIs = null; // clears a previous call to this method
$it = new FilesystemIterator(pathToAPIs);
foreach ($it as $fileinfo){
$filename = pathinfo($fileinfo->getFilename(), PATHINFO_FILENAME); // removes extension
$this->listOfAPIs[]= $filename;
}
}
public function __call($method,$args){
}
}
API/exampleExternalAPI.php
<?php
class exampleExternalAPI {
public function echoString($string){
echo $string;
}
}
example.php
<?php
require_once 'api.php';
$api = new api();
$api->setAPIs('exampleExternalAPI');
$api->APIs->exampleExternalAPI->echoString('string');
Background Info
(may give some insight to my madness)
I'm working on a project where I need to connect to lots of external APIs.
So I decided to creating a class to look after all my communications with external APIs ( not sure if best way - new to Object Oriented Programming).
I'm not entirely sure what problem you're trying to solve, but if your APIs is a simple stdClass instance it should work as expected:
public function setAPIs($setAPIs)
{
$this->APIs = new stdClass; // clears a previous call to this method
if (!is_array($setAPIs)) { // if not an array
$setAPIs = array($setAPIs); // make array
}
foreach ($setAPIs as $setAPIType) {
if (in_array($setAPIType, $this->listOfAPIs)) {
$this->APIs->{$setAPIType} = new $setAPIType;
}
}
return $this->APIs;
}

Add caching to Zend\Form\Annotation\AnnotationBuilder

As I've finally found a binary of memcache for PHP 5.4.4 on Windows, I'm speeding up the application I'm currently developing.
I've succeeded setting memcache as Doctrine ORM Mapping Cache driver, but I need to fix another leakage: Forms built using annotations.
I'm creating forms according to the Annotations section of the docs. Unfortunately, this takes a lot of time, especially when creating multiple forms for a single page.
Is it possible to add caching to this process? I've browsed through the code but it seems like the Zend\Form\Annotation\AnnotationBuilder always creates the form by reflecting the code and parsing the annotations. Thanks in advance.
You might wanna try something like this:
class ZendFormCachedController extends Zend_Controller_Action
{
protected $_formId = 'form';
public function indexAction()
{
$frontend = array(
'lifetime' => 7200,
'automatic_serialization' => true);
$backend = array('cache_dir' => '/tmp/');
$cache = Zend_Cache::factory('Core', 'File', $frontend, $backend);
if ($this->getRequest()->isPost()) {
$form = $this->getForm(new Zend_Form);
} else if (! $form = $cache->load($this->_formId)) {
$form = $this->getForm(new Zend_Form);
$cache->save($form->__toString(), $this->_formId);
}
$this->getHelper('layout')->setLayout('zend-form');
$this->view->form = $form;
}
Found here.
Louis's answer didn't work for me so what I did was simply extend AnnotationBuilder's constructor to take a cache object and then modified getFormSpecification to use that cache to cache the result. My function is below..
Very quick work around...sure it could be improved. In my case, I was limited to some old hardware and this took the load time on a page from 10+ seconds to about 1 second
/**
* Creates and returns a form specification for use with a factory
*
* Parses the object provided, and processes annotations for the class and
* all properties. Information from annotations is then used to create
* specifications for a form, its elements, and its input filter.
*
* MODIFIED: Now uses local cache to store parsed annotations
*
* #param string|object $entity Either an instance or a valid class name for an entity
* #throws Exception\InvalidArgumentException if $entity is not an object or class name
* #return ArrayObject
*/
public function getFormSpecification($entity)
{
if (!is_object($entity)) {
if ((is_string($entity) && (!class_exists($entity))) // non-existent class
|| (!is_string($entity)) // not an object or string
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an object or valid class name; received "%s"',
__METHOD__,
var_export($entity, 1)
));
}
}
$formSpec = NULL;
if ($this->cache) {
//generate cache key from entity name
$cacheKey = (is_string($entity) ? $entity : get_class($entity)) . '_form_cache';
//get the cached form annotations, try cache first
$formSpec = $this->cache->getItem($cacheKey);
}
if (empty($formSpec)) {
$this->entity = $entity;
$annotationManager = $this->getAnnotationManager();
$formSpec = new ArrayObject();
$filterSpec = new ArrayObject();
$reflection = new ClassReflection($entity);
$annotations = $reflection->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
}
foreach ($reflection->getProperties() as $property) {
$annotations = $property->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureElement($annotations, $property, $formSpec, $filterSpec);
}
}
if (!isset($formSpec['input_filter'])) {
$formSpec['input_filter'] = $filterSpec;
}
//save annotations to cache
if ($this->cache) {
$this->cache->addItem($cacheKey, $formSpec);
}
}
return $formSpec;
}

Categories