https://github.com/andrewjwolf/payjunction-php
I ran into some issues with unit testing in that getting the response body etc from the curl handle was tricky. I didn't want to necessarily use a mock or abstract the curl functionality.
A colleague of mine suggested that I just use the localhost as an endpoint and reflect back the request for unit testing. I thought this was a novel approach.
The directories in question are /test/echo/ and /test/unit/
Any feedback would be great. I'm also interested in seeing if anyone had any opinions on the assertions.
the echo endpoint:
<?php
header('Content-Type: application/json');
$data = array(
'headers' => getallheaders(),
// 'server' => $_SERVER,
'request_method' => $_SERVER['REQUEST_METHOD'],
'get' => $_GET,
'post' => $_POST,
'put' => $_POST,
);
//If the request is a put then get the file contents and try to parse the string into an array
if($data['request_method'] == 'PUT')
{
parse_str(file_get_contents("php://input"), $put_data);
$data['put'] = $put_data;
}
echo json_encode($data);
An example of one of the unit tests:
<?php
class TransactionTest extends PHPUnit_Framework_TestCase{
static $endpoint = 'http://localhost/payjunctionphp/test/echo';
public function setUp()
{
$options = array(
'username' => 'pj-ql-01',
'password' => 'pj-ql-01p',
'appkey' => '2489d40d-a74f-474f-9e8e-7b39507f3101'
);
parent::setUp();
$this->client = new TransactionClient($options);
$this->client->setEndpoint(self::$endpoint);
}
private function getRequestPath($client = null)
{
if(!isset($client)) $client = $this->client;
return str_replace($client->baseUrl,'',curl_getinfo($client->curl)['url']);
}
/**
* Ensure that the correct verb and path are used for the create method
*/
public function testCreate()
{
$data = array(
'achRoutingNumber' => '987654321',
'achAccountNumber' => '123456789',
'achAccountType' => 'CHECKING',
'foo' => 'bar'
);
$transaction = $this->client->create($data);
$this->assertEquals($data, get_object_vars($transaction->post),'Passed variables are not correct');
$this->assertEquals('POST', $transaction->request_method,'The PHP Verb Is Incorrect');
$this->assertEquals('/transactions', $this->getRequestPath(), 'The path is incorrect');
}
/**
* Ensure that the correct verb and path are used for the read method
*/
public function testRead()
{
$transaction = $this->client->read(543);
$this->assertEquals('GET', $transaction->request_method,'The PHP Verb Is Incorrect');
$this->assertEquals('/transactions/543', $this->getRequestPath(), 'The path is incorrect');
}
/**
* Ensure that the correct verb and path are used for the read method
*/
public function testUpdate()
{
$data = array(
'foo' => 'baz'
);
$transaction = $this->client->Update(654,$data);
$this->assertEquals($data, get_object_vars($transaction->put),'Passed variables are not correct');
$this->assertEquals('PUT', $transaction->request_method,'The PHP Verb Is Incorrect');
$this->assertEquals('/transactions/654', $this->getRequestPath(), 'The path is incorrect');
}
/**
* Ensure that the correct verb and path are used for the read method
*/
public function testAddSignature()
{
$data = array(
'foo' => 'baa'
);
$transaction = $this->client->addSignature(655,$data);
$this->assertEquals($data, get_object_vars($transaction->post),'Passed variables are not correct');
$this->assertEquals('POST', $transaction->request_method,'The PHP Verb Is Incorrect');
$this->assertEquals('/transactions/655/signature/capture', $this->getRequestPath(), 'The path is incorrect');
}
}
The base model from which the various clients extend from:
<?php
class PayjunctionClient
{
public $liveEndpoint = 'https://api.payjunction.com';
public $testEndpoint = 'https://api.payjunctionlabs.com';
public $packageVersion = '0.0.1';
public $userAgent;
public function __construct()
{
$this->userAgent = 'PayJunctionPHPClient/' . $this->packageVersion . '(BrandedCreate; PHP/)'; //@todo add process.version
$this->baseUrl = $this->testEndpoint;
}
public function setEndpoint($endpoint)
{
$this->baseUrl = $endpoint;
}
/**
* @description initializes the curl handle with default configuration and settings
* @param null $handle
* @return $this
*/
public function initCurl($handle = null)
{
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false); //Don't worry about validating ssl @todo talk about security concerns
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
//if we have a password and username then set it by default to be passed for authentication
if (isset($this->defaults['password']) && isset($this->defaults['username'])) {
curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($this->curl, CURLOPT_USERPWD, $this->defaults['username'] . ":" . $this->defaults['password']);
}
//if we have default headers to pass then pass them
if (isset($this->defaults['headers']) && is_array($this->defaults['headers'])) {
$headers = array();
foreach ($this->defaults['headers'] as $key => $value) {
array_push($headers, $key . ': ' . $value);
}
curl_setopt($this->curl, CURLOPT_HTTPHEADER, $headers);
}
return $this;
}
/**
* @description generates a new client
* @param null $options
* @return $this
*/
public function generateClient($options = null)
{
$this->baseUrl = isset($options['endpoint']) ? $options['endpoint'] : $this->baseUrl;
$this->defaults['username'] = isset($options['username']) ? $options['username'] : '';
$this->defaults['password'] = isset($options['password']) ? $options['password'] : '';
$this->defaults['headers']['X-PJ-Application-Key'] = isset($options['appkey']) ? $options['appkey'] : '';
$this->defaults['headers']['User-Agent'] = $this->userAgent;
$this->initCurl();
return $this;
}
/**
* @description takes the response from our curl request and turns it into an object if necessary
* @param $response
* @param null $contentType
* @return array|mixed
*/
public function processResponse($response)
{
$contentType = curl_getinfo($this->curl, CURLINFO_CONTENT_TYPE);
if ($contentType == 'text/html' || is_null($contentType) || !isset($contentType) || $contentType = '' || $contentType == FALSE) {
return $response;
}
try {
$object = json_decode($response);
return $object;
} catch (Exception $e) {
return array(
'errors' => array(
0 => 'Invalid Response Type, Error In Processing Response From Payjunction'
)
);
}
}
/**
* @description processes a curl post request
* @param $path
* @param null $params
* @return array|mixed
*/
public function post($path, $params = null)
{
curl_setopt($this->curl, CURLOPT_POST, TRUE);
curl_setopt($this->curl, CURLOPT_URL, $this->baseUrl . $path);
if (is_object($params) || is_array($params)) {
curl_setopt($this->curl, CURLOPT_POSTFIELDS, http_build_query($params));
}
return $this->processResponse(curl_exec($this->curl));
}
/**
* @description processes a curl get request
* @param $path
* @param null $params
* @return array|mixed
*/
public function get($path, $params = null)
{
//create the query string if there are any parameters that need to be passed
$query_string = "";
if (!is_null($params)) {
$query_string = "?" . http_build_query($params,'','&');
}
curl_setopt($this->curl, CURLOPT_HTTPGET, TRUE);
curl_setopt($this->curl, CURLOPT_URL, $this->baseUrl . $path . $query_string);
return $this->processResponse(curl_exec($this->curl));
}
/**
* @description processes a curl put request
* @param $path
* @param null $params
* @return array|mixed
*/
public function put($path, $params = null)
{
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "PUT");
if (is_object($params) || is_array($params)) {
curl_setopt($this->curl, CURLOPT_POSTFIELDS, http_build_query($params));
}
curl_setopt($this->curl, CURLOPT_URL, $this->baseUrl . $path);
return $this->processResponse(curl_exec($this->curl));
}
/**
* @description processes a curl delete request
* @param $path
* @param null $params
* @return array|mixed
*/
public function del($path, $params = null)
{
curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, "DELETE");
if (is_object($params) || is_array($params)) {
curl_setopt($this->curl, CURLOPT_POSTFIELDS, http_build_query($params));
}
curl_setopt($this->curl, CURLOPT_URL, $this->baseUrl . $path);
return $this->processResponse(curl_exec($this->curl));
}
}
The TransactionClient related specifically to this unit test:
<?php
class TransactionClient extends PayjunctionClient
{
public function __construct($options)
{
parent::__construct();
$this->generateClient($options);
}
/**
* @description create a new transaction
* @param $params
* @return array|mixed
*/
public function create($params)
{
return $this->post('/transactions',$params);
}
/**
* @description read from an existing transaction
* @param $id
* @return array|mixed
*/
public function read($id)
{
return $this->get('/transactions/'.$id);
}
/**
* @description update an existing transaction
* @param $id
* @param null $params
* @return array|mixed
*/
public function update($id, $params = null)
{
return $this->put('/transactions/'.$id, $params);
}
/**
* @todo this does not appear to be working 405 Method Not Allowed
* @description add a signature to an existing transaction
* @param $id
* @param $params
* @return array|mixed
*/
public function addSignature($id, $params)
{
return $this->post('/transactions/'.$id.'/signature/capture',$params);
}
}