Using PHPUnit to test cookies and sessions, how? - php

With PHPUnit it's quite easy to test raw PHP code, but what about code that heavily relies on cookies? Sessions could be a good example.
Is there a method that doesn't require me to setup $_COOKIE with data during my test? It feels like a hacky way of doing things.

This is a common problem with code, especially lagacy PHP code. The common technique used is to further abstract the COOKIE/SESSION variables in related objects and using inversion of control technique(s) to pull those dependencies into scope.
http://martinfowler.com/articles/injection.html
Now before you execute a test you would instantiate a mock version of a Cookie/Session object and provide default data.
I imagine, the same effect can be achieved with legacy code by simply overriding the super global value before executing the test.
Cheers,
Alex

I understand this is quite old, but I believe this needs to be updated as technology has improved since the original post. I was able to get sessions working with this solution using php 5.4 with phpunit 3.7:
class UserTest extends \PHPUnit_Framework_TestCase {
//....
public function __construct () {
ob_start();
}
protected function setUp() {
$this->object = new \User();
}
public function testUserLogin() {
$this->object->setUsername('test');
$this->object->setPassword('testpw');
// sets the session within:
$this->assertEquals(true, $this->object->login());
}
}

I found that I could use PHPUnit to test the behavior of the part of my website that relies heavily on sessions, through a combination of Curl and a cookie that passes the session id.
The following Curl class uses the CURLOPT_COOKIE option to pass a session parameter. The static variable $sessionid saves the session between different Curl calls. Further, sessions can be changed using the static function changeSession.
class Curl {
private $ch;
private static $sessionid;
public function __construct($url, $options) {
$this->ch = curl_init($url);
if (!self::$sessionid)
self::$sessionid = .. generateRandomString() ..;
$options = $options + array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIE => 'PHPSESSID=' . self::$sessionid);
foreach ($options as $key => $val) {
curl_setopt($this->ch, $key, $val);
}
}
public function getResponse() {
if ($this->response) {
return $this->response;
}
$response = curl_exec($this->ch);
$error = curl_error($this->ch);
$errno = curl_errno($this->ch);
$header_size = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE);
$this->header = substr($response, 0, $header_size);
$response = substr($response, $header_size);
if (is_resource($this->ch)) {
curl_close($this->ch);
}
if (0 !== $errno) {
throw new \RuntimeException($error, $errno);
}
return $this->response = $response;
}
public function __toString() {
return $this->getResponse();
}
public static function changeSession() {
self::$SESSIONID = Practicalia::generateRandomString();
}
}
An example call
$data = array(
'action' => 'someaction',
'info' => 'someinfo'
);
$curl = new Curl(
'http://localhost/somephp.php',
array(
CURLOPT_POSTFIELDS => http_build_query($data)));
$response = $curl->getResponse();
And any subsequent Curl calls will automatically use the same session as the previous one, unless specifically Curl::changeSession() is called.

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/*',
];
}

Mocking external library in PHP/Codeigniter

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!

web service soap call - how to get returning parameter

I am creating web service. So I created this soapcall:
public function GetLogon($uzivatel, $heslo){
$soap = new SoapClient('http://www.softhouse.cz/ezopconnector2/ezopconnector.asmx?wsdl');
$params = array('uzivatel'=>$uzivatel, 'password'=>$heslo);
$response = $soap->__soapCall("EzopLogon", array($params));
var_dump($response);
}
call it in presenter like this, but it's not relevant I think:
$this->me->GetLogon("administrator", "a");
my problem is that this function should return me a session (of user).. does anybody know how can I get this session for future use? (for example for logout)
thanks a lot I am a novice, so dont yel at me :D
Update:
code for login:
public function GetLogon($uzivatel, $heslo){
$soapClient = new SoapClient('http://www.softhouse.cz/ezopconnector2/ezopconnector.asmx?wsdl');
$params = array('uzivatel'=>$uzivatel, 'heslo'=>$heslo);
$this->session = $soapClient->__soapCall("EzopLogon", array($params));
var_dump($this->session);
}
session saved as public variable in class:
public $session;
Code of function where session si required:
public function GetCtiSezSdruzAdd2(){
$soapClient = new SoapClient('http://www.softhouse.cz/ezopconnector2/ezopconnector.asmx?wsdl');
$params = array('Session'=>$this->session);
return $soapClient->__soapCall("EzopCtiSeznamSdruzenychAdresaru", array($params));
}
and call in presenter:
$this->me->GetCtiSezSdruzAdd2();

php zookeeper watcher doesn't work

I'm trying to use zookeeper in a php app, and I've done most of the get($path)/set($path, $value)/getChildren($path) functions following https://github.com/andreiz/php-zookeeper, except the watch_callback function that just doesn't work.
My php version is 5.6.14 and thread safety disabled, and I'm using apache2.4.
Here is some code snippet
class Zookeeper_Module {
private $zookeeper;
public function __construct(){
$this->ci = & get_instance();
$zookeeper_server = $this->ci->config->item('zookeeper_server');
$this->zookeeper = new Zookeeper($zookeeper_server);
}
public function set($path, $value){
$this->zookeeper->set($path, $value);
}
public function get($path, $watch_cb = null){
return $this->zookeeper->get($path, $watch_cb);
}
public function get_watch_cb($event_type = '', $stat = '', $path = ''){
error_log('hello from get_watcher_cb');
$value = $this->get($path, array($this, 'get_watch_cb'));
// update redis cache
$this->ci->cache->redis->save('some cache key', $value);
}
}
class MyTest{
public function get(){
$zookeeper = new Zookeeper_Module ();
$value = $zookeeper->get( '/foo/bar', array (
$zookeeper,
'get_watch_cb'
) );
}
public function set(){
$zookeeper = new Zookeeper_Module ();
$zookeeper->set( '/foo/bar', 'some value');
}
}
I can successfully get or set a node value, but I can neither catch watch callback log nor have redis cache updated.
I write a more simple demo, very similar with this https://github.com/andreiz/php-zookeeper/wiki, and the watcher works fine in this demo.
The most significant difference is
while( true ) {
echo '.';
sleep(2);
}
While java has a jvm container hosting watchers, php doesn't has a container to do it, so we have to use a while(true) to keep watchers alive.
So I add a while(true) in my code and now the watcher works fine.
But I don't want to add a terrible while(true) in a web app, so the final solution is adding a java app to communicate with zookeeper and save results in redis, and php app just reads info from redis.

PHP cURL - thread safe?

I wrote a PHP script which retrieved data via libcurl and processed it. It worked fine but for performance reasons I changed it to use dozens of workers (threads). The performance improved by more than 50 times, however now php.exe is crashing every few minutes and the faulting module listed is php_curl.dll. I do have prior experience with multi-threading in C, but haven't used it at all before in php.
I googled around and supposedly cURL is thread safe (as of 2001):
http://curl.haxx.se/mail/lib-2001-01/0001.html
But I can't find any mention of whether or not php_curl is thread safe.
In case it matters, I am running php from the command line. My setup is Win7 x64, PHP 5.5.11 Thread Safe VC11 x86, PHP pthreads 2.0.4 for PHP 5.5 Thread Safe VC11 x86.
Here is some pseudo code to show what I am doing
class MyWorker extends Worker
{
...
public function run()
{
...
while(1)
{
...
runCURL();
...
sleep(1);
}
}
}
function runCURL()
{
static $curlHandle = null;
...
if(is_null($curlHandle))
{
$curlHandle = curl_init();
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curlHandle, CURLOPT_USERAGENT, "My User Agent String");
}
curl_setopt($curlHandle, CURLOPT_URL, "The URL");
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $data);
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $header);
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, false);
$result = curl_exec($curlHandle);
...
}
Firstly, resource types are officially unsupported by pthreads; a curl handle is a resource, you therefore should not store curl handles in the object scope of pthreads objects, since they might become corrupted.
Making it easy
pthreads provides an easy way to use workers...
The easiest way to execute among many threads is to use the built in Pool class provided by pthreads:
http://php.net/pool
The following code demonstrates how to pool a bunch of requests in a few background threads:
<?php
define("LOG", Mutex::create());
function slog($message, $args = []) {
$args = func_get_args();
if (($message = array_shift($args))) {
Mutex::lock(LOG);
echo vsprintf("{$message}\n", $args);
Mutex::unlock(LOG);
}
}
class Request extends Threaded {
public function __construct($url, $post = []) {
$this->url = $url;
$this->post = $post;
}
public function run() {
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_URL, $this->url);
if ($this->post) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $this->post);
}
$response = curl_exec($curl);
slog("%s returned %d bytes", $this->url, strlen($response));
}
public function getURL() { return $this->url; }
public function getPost() { return $this->post; }
protected $url;
protected $post;
}
$max = 100;
$urls = [];
while (count($urls) < $max) {
$urls[] = sprintf(
"http://www.google.co.uk/?q=%s",
md5(mt_rand()*count($urls)));
}
$pool = new Pool(4);
foreach ($urls as $url) {
$pool->submit(new Request($url));
}
$pool->shutdown();
Mutex::destroy(LOG);
?>
Your specific task requires that you now process the data, you can either write this functionality into a design like the above ... or
Making it fancy
promises are a super fancy form of concurrency ...
Promises suit the nature of the task here:
First: Make a request
Then: Process response
The following code shows how to use pthreads/promises to make the same request and process responses:
<?php
namespace {
require_once("vendor/autoload.php");
use pthreads\PromiseManager;
use pthreads\Promise;
use pthreads\Promisable;
use pthreads\Thenable;
define("LOG", Mutex::create());
function slog($message, $args = []) {
$args = func_get_args();
if (($message = array_shift($args))) {
Mutex::lock(LOG);
echo vsprintf("{$message}\n", $args);
Mutex::unlock(LOG);
}
}
/* will be used by everything to report errors when they occur */
trait ErrorManager {
public function onError(Promisable $promised) {
slog("Oh noes: %s\n", (string) $promised->getError());
}
}
class Request extends Promisable {
use ErrorManager;
public function __construct($url, $post = []) {
$this->url = $url;
$this->post = $post;
$this->done = false;
}
public function onFulfill() {
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_URL, $this->url);
if ($this->post) {
curl_setopt($curl, CURLOPT_POSTFIELDS, $this->post);
}
$this->response = curl_exec($curl);
}
public function getURL() { return $this->url; }
public function getPost() { return $this->post; }
public function getResponse() { return $this->response; }
public function setGarbage() { $this->garbage = true; }
public function isGarbage() { return $this->garbage; }
protected $url;
protected $post;
protected $response;
protected $garbage;
}
class Process extends Thenable {
use ErrorManager;
public function onFulfilled(Promisable $request) {
slog("%s returned %d bytes\n",
$request->getURL(), strlen($request->getResponse()));
}
}
/* some dummy urls */
$max = 100;
$urls = [];
while (count($urls) < $max) {
$urls[] = sprintf(
"http://www.google.co.uk/?q=%s",
md5(mt_rand()*count($urls)));
}
/* initialize manager for promises */
$manager = new PromiseManager(4);
/* create promises to make and process requests */
while (#++$id < $max) {
$promise = new Promise($manager, new Request($urls[$id], []));
$promise->then(
new Process($promise));
}
/* force the manager to shutdown (fulfilling all promises first) */
$manager->shutdown();
/* destroy mutex */
Mutex::destroy(LOG);
}
?>
Composer:
{
"require": {
"krakjoe/promises": ">=1.0.2"
}
}
Note that Request has hardly changed, all that has been added is somewhere to hold the response and a means to detect if the objects are garbage.
For details on garbage collection from pools, which applies to both examples:
http://php.net/pool.collect
The slog function exists only to make logged output readable
Making it clear
pthreads is not a new PDO driver ...
Many people approach using pthreads as they would approach using a new PDO driver - assume it works like the rest of PHP and that everything will be fine.
Everything might not be fine, and requires research: we are pushing the envelope, in doing so some "restrictions" must be placed upon the architecture of pthreads to maintain stability, this can have some strange side effects.
While pthreads comes with exhaustive documentation which mostly include examples in the PHP manual, I'm not able to attach the following document in the manual, yet.
The following document provides you with an understanding of the internals of pthreads, everyone should read it, it's written for you.
https://gist.github.com/krakjoe/6437782

Categories