Last active
December 25, 2015 05:09
-
-
Save Vinai/6922140 to your computer and use it in GitHub Desktop.
PHPUnit bootstrap example for OrderManagement
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$path = __DIR__ . '/../'; | |
$mage = 'app/Mage.php'; | |
$i = 0; | |
while (! file_exists($path . $mage) && ! file_exists($path . 'htdocs/' . $mage) && $i++ < 10) { | |
$path .= '../'; | |
} | |
chdir(file_exists($path . $mage) ? $path : $path . 'htdocs'); | |
require_once $mage; | |
// require_once 'vendor/autoload.php'; | |
Mage::setIsDeveloperMode(true); | |
Mage::app('admin'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Training_OrderIntegrationClient_Model_Client | |
{ | |
protected $_socket; | |
/** | |
* Workaround to make class testable | |
* | |
* @var bool $_doReadOnSend | |
*/ | |
protected $_doReadOnSend = true; | |
protected function _initSocket() | |
{ | |
if (! $this->_socket) { | |
$ip = Mage::getStoreConfig('training/orderintegration_client/server_ip'); | |
$port = Mage::getStoreConfig('training/orderintegration_client/server_port'); | |
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
if (! $socket ||! socket_connect($socket, $ip, $port)) { | |
Mage::throwException("Unable to connect to OrderIntegration server $ip:$port"); | |
} | |
$this->_socket = $socket; | |
} | |
} | |
protected function _send($msg) | |
{ | |
$this->_initSocket(); | |
if (mb_substr($msg, -1) !== "\n") { | |
$msg .= "\n"; | |
} | |
$result = false !== socket_write($this->_socket, $msg, mb_strlen($msg)); | |
if ($this->_doReadOnSend) { | |
$result = trim($this->_read()); | |
} | |
return $result; | |
} | |
protected function _read() | |
{ | |
$this->_initSocket(); | |
$response = socket_read($this->_socket, 4096); | |
return $response; | |
} | |
/** | |
* Workaround to make class testable | |
* | |
* @param $flag | |
*/ | |
public function setDoReadOnSend($flag) | |
{ | |
$this->_doReadOnSend = (bool) $flag; | |
} | |
public function close() | |
{ | |
if ($this->_socket) { | |
socket_close($this->_socket); | |
} | |
} | |
public function push(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to push unsaved order model!\n"); | |
} | |
$result = $this->_send("pull {$order->getId()}"); | |
return '1' === $result; | |
} | |
public function canInvoice(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to check capability for unsaved order model!\n"); | |
} | |
$result = $this->_send("availability {$order->getId()} invoice"); | |
return '1' === $result; | |
} | |
public function invoice(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to check capability for unsaved order model!\n"); | |
} | |
$this->_send("sync off"); | |
$result = $this->_send("invoice {$order->getId()}"); | |
$this->_send("sync on"); | |
return '1' === $result; | |
} | |
public function canShip(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to check capability for unsaved order model!\n"); | |
} | |
$result = $this->_send("availability {$order->getId()} ship"); | |
return '1' === $result; | |
} | |
public function ship(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to check capability for unsaved order model!\n"); | |
} | |
$this->_send("sync off"); | |
$result = $this->_send("ship {$order->getId()}"); | |
$this->_send("sync on"); | |
return '1' === $result; | |
} | |
public function canRefund(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to check capability for unsaved order model!\n"); | |
} | |
$result = $this->_send("availability {$order->getId()} refund"); | |
return '1' === $result; | |
} | |
public function refund(Mage_Sales_Model_Order $order) | |
{ | |
if (! $order->getId()) { | |
Mage::throwException("Unable to check capability for unsaved order model!\n"); | |
} | |
$this->_send("sync off"); | |
$result = $this->_send("refund {$order->getId()}"); | |
$this->_send("sync on"); | |
return '1' === $result; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class ClientTest extends PHPUnit_Framework_TestCase | |
{ | |
protected $sock; | |
protected $client; | |
protected $order; | |
protected function setUp() | |
{ | |
$ip = '127.0.0.1'; | |
$port = '0'; | |
$this->initSocket($ip, $port); | |
$this->setServerConfig($ip, $port); | |
$this->mockOrder(); | |
} | |
protected function initSocket(&$ip, &$port) | |
{ | |
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); | |
socket_bind($sock, $ip, $port); | |
socket_listen($sock, 3); | |
$this->sock = $sock; | |
// Update the port number to what was assigned for 0 | |
socket_getsockname($sock, $ip, $port); | |
} | |
protected function setServerConfig($ip, $port) | |
{ | |
Mage::app()->getStore() | |
->setConfig('training/orderintegration_client/server_ip', $ip) | |
->setConfig('training/orderintegration_client/server_port', $port); | |
} | |
protected function mockOrder() | |
{ | |
$order = $this->getMock('Mage_Sales_Model_Order'); | |
$order->expects($this->exactly(2)) | |
->method('getId') | |
->will($this->returnValue(1)); | |
$this->order = $order; | |
} | |
protected function getClient() | |
{ | |
$client = Mage::getModel('training_orderIntegrationClient/client'); | |
$client->setDoReadOnSend(false); | |
return $client; | |
} | |
protected function readSentData() | |
{ | |
if (! isset($this->client)) { | |
$this->client = socket_accept($this->sock); | |
} | |
$buffer = socket_read($this->client, 2048, PHP_BINARY_READ); | |
return $buffer; | |
} | |
public function testPush() | |
{ | |
$this->getClient()->push($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("pull 1\n", $result); | |
} | |
public function testCanInvoice() | |
{ | |
$this->getClient()->canInvoice($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("availability 1 invoice\n", $result); | |
} | |
public function testInvoice() | |
{ | |
$this->getClient()->invoice($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("sync off\ninvoice 1\nsync on\n", $result); | |
} | |
public function testCanShip() | |
{ | |
$this->getClient()->canShip($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("availability 1 ship\n", $result); | |
} | |
public function testShip() | |
{ | |
$this->getClient()->ship($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("sync off\nship 1\nsync on\n", $result); | |
} | |
public function testCanRefund() | |
{ | |
$this->getClient()->canRefund($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("availability 1 refund\n", $result); | |
} | |
public function testRefund() | |
{ | |
$this->getClient()->refund($this->order); | |
usleep(50000); // wait 0.05 sec | |
$result = $this->readSentData(); | |
$this->assertEquals("sync off\nrefund 1\nsync on\n", $result); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0"?> | |
<config> | |
<global> | |
<models> | |
<training_orderIntegrationClient> | |
<class>Training_OrderIntegrationClient_Model</class> | |
</training_orderIntegrationClient> | |
</models> | |
<events> | |
<sales_model_service_quote_submit_success> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesModelServiceQuoteSubmitSuccess</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_model_service_quote_submit_success> | |
<sales_order_save_commit_after> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderSaveCommitAfter</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_save_commit_after> | |
<sales_order_invoice_save_before> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderInvoiceSaveBefore</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_invoice_save_before> | |
<sales_order_invoice_save_commit_after> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderInvoiceSaveCommitAfter</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_invoice_save_commit_after> | |
<sales_order_shipment_save_before> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderShipmentSaveBefore</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_shipment_save_before> | |
<sales_order_shipment_save_commit_after> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderShipmentSaveCommitAfter</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_shipment_save_commit_after> | |
<sales_order_creditmemo_save_before> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderCreditmemoSaveBefore</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_creditmemo_save_before> | |
<sales_order_creditmemo_save_commit_after> | |
<observers> | |
<training_orderIntegrationClient> | |
<class>training_orderIntegrationClient/observer</class> | |
<method>salesOrderCreditmemoSaveCommitAfter</method> | |
</training_orderIntegrationClient> | |
</observers> | |
</sales_order_creditmemo_save_commit_after> | |
</events> | |
</global> | |
<default> | |
<training> | |
<orderintegration_client> | |
<server_ip>127.0.0.1</server_ip> | |
<server_port>8888</server_port> | |
</orderintegration_client> | |
</training> | |
</default> | |
</config> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class Training_OrderIntegrationClient_Model_Observer | |
{ | |
const NEW_FLAG = 'orderintegration_object_new_flag'; | |
/** | |
* @return Training_OrderIntegrationClient_Model_Client | |
*/ | |
protected function _getClient() | |
{ | |
return Mage::getSingleton('training_orderIntegrationClient/client'); | |
} | |
public function salesModelServiceQuoteSubmitSuccess($args) | |
{ | |
$order = $args->getOrder(); | |
$this->_getClient()->push($order); | |
} | |
public function salesOrderSaveCommitAfter($args) | |
{ | |
$order = $args->getOrder(); | |
$this->_getClient()->push($order); | |
} | |
public function salesOrderInvoiceSaveBefore($args) | |
{ | |
$invoice = $args->getInvoice(); | |
if ($invoice->isObjectNew()) { | |
$invoice->setData(self::NEW_FLAG, true); | |
$order = $invoice->getOrder(); | |
if (! $this->_getClient()->canInvoice($order)) { | |
Mage::throwException("Synchronization Error: invoice capability not available for order {$order->getId()}"); | |
} | |
} | |
} | |
public function salesOrderInvoiceSaveCommitAfter($args) | |
{ | |
$invoice = $args->getInvoice(); | |
$order = $invoice->getOrder(); | |
if ($invoice->getData(self::NEW_FLAG)) { | |
$invoice->unsetData(self::NEW_FLAG); | |
if (! $this->_getClient()->invoice($order)) { | |
Mage::throwException("Synchronization Error: unable to synchronize invoice for order {$order->getId()}"); | |
} | |
} else { | |
$this->_getClient()->push($order); | |
} | |
} | |
public function salesOrderShipmentSaveBefore($args) | |
{ | |
$shipment = $args->getShipment(); | |
if ($shipment->isObjectNew()) { | |
$shipment->setData(self::NEW_FLAG, true); | |
$order = $shipment->getOrder(); | |
if (! $this->_getClient()->canShip($order)) { | |
Mage::throwException("Synchronization Error: ship capability not available for order {$order->getId()}"); | |
} | |
} | |
} | |
public function salesOrderShipmentSaveCommitAfter($args) | |
{ | |
$shipment = $args->getShipment(); | |
$order = $shipment->getOrder(); | |
if ($shipment->getData(self::NEW_FLAG)) { | |
$shipment->unsetData(self::NEW_FLAG); | |
if (! $this->_getClient()->ship($order)) { | |
Mage::throwException("Synchronization Error: unable to synchronize shipment for order {$order->getId()}"); | |
} | |
} else { | |
$this->_getClient()->push($order); | |
} | |
} | |
public function salesOrderCreditmemoSaveBefore($args) | |
{ | |
$creditmemo = $args->getCreditmemo(); | |
if ($creditmemo->isObjectNew()) { | |
$creditmemo->setData(self::NEW_FLAG, true); | |
$order = $creditmemo->getOrder(); | |
if (! $this->_getClient()->canRefund($order)) { | |
Mage::throwException("Synchronization Error: refund capability not available for order {$order->getId()}"); | |
} | |
} | |
} | |
public function salesOrderCreditmemoSaveCommitAfter($args) | |
{ | |
$creditmemo = $args->getCreditmemo(); | |
$order = $creditmemo->getOrder(); | |
if ($creditmemo->getData(self::NEW_FLAG)) { | |
$creditmemo->unsetData(self::NEW_FLAG); | |
if (! $this->_getClient()->refund($order)) { | |
Mage::throwException("Synchronization Error: unable to synchronize refund for order {$order->getId()}"); | |
} | |
} else { | |
$this->_getClient()->push($order); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class ObserverTest extends PHPUnit_Framework_TestCase | |
{ | |
/** @var PHPUnit_Framework_MockObject_MockObject */ | |
protected $mockClient; | |
protected function setUp() | |
{ | |
$this->mockApiClient(); | |
$this->mockCustomerSession(); | |
$this->setCurrentCurrency(); | |
} | |
/** | |
* @return Mage_Sales_Model_Order | |
*/ | |
public function testOrderPlacement() | |
{ | |
$this->mockClient | |
->expects($this->atLeastOnce()) | |
->method('push') | |
->will($this->returnValue(true)); | |
$quote = $this->getQuoteFixture(); | |
$service = Mage::getModel('sales/service_quote', $quote); | |
$order = $service->submitOrder(); | |
return $order; | |
} | |
/** | |
* @param Mage_Sales_Model_Order $order | |
* @test | |
* @depends testOrderPlacement | |
*/ | |
public function testOrderUpdated(Mage_Sales_Model_Order $order) | |
{ | |
$this->mockClient | |
->expects($this->atLeastOnce()) | |
->method('push') | |
->will($this->returnValue(true)); | |
$order->setStatus('test')->save(); | |
} | |
/** | |
* @param Mage_Sales_Model_Order $order | |
* @returns Mage_Sales_Model_Order | |
* @test | |
* @depends testOrderPlacement | |
*/ | |
public function testOrderInvoiced(Mage_Sales_Model_Order $order) | |
{ | |
$this->mockClient | |
->expects($this->once()) | |
->method('canInvoice') | |
->will($this->returnValue(true)); | |
$this->mockClient | |
->expects($this->once()) | |
->method('invoice') | |
->will($this->returnValue(true)); | |
$order->prepareInvoice() | |
->register() | |
->save(); | |
$order->save(); | |
return $order; | |
} | |
/** | |
* @param Mage_Sales_Model_Order $order | |
* @test | |
* @depends testOrderPlacement | |
*/ | |
public function testOrderShipped(Mage_Sales_Model_Order $order) | |
{ | |
$this->mockClient | |
->expects($this->once()) | |
->method('canShip') | |
->will($this->returnValue(true)); | |
$this->mockClient | |
->expects($this->once()) | |
->method('ship') | |
->will($this->returnValue(true)); | |
$order->prepareShipment() | |
->register() | |
->save(); | |
} | |
/** | |
* @param Mage_Sales_Model_Order $order | |
* @test | |
* @depends testOrderInvoiced | |
*/ | |
public function testOrderRefunded(Mage_Sales_Model_Order $order) | |
{ | |
$this->mockClient | |
->expects($this->once()) | |
->method('canRefund') | |
->will($this->returnValue(true)); | |
$this->mockClient | |
->expects($this->once()) | |
->method('refund') | |
->will($this->returnValue(true)); | |
$service = Mage::getModel('sales/service_order', $order); | |
$service->prepareCreditmemo() | |
->refund() | |
->save(); | |
} | |
/* ---------------------------- */ | |
protected function setSingleton($object, $type) | |
{ | |
$key = '_singleton/' . $type; | |
Mage::unregister($key); | |
Mage::register($key, $object); | |
} | |
protected function mockApiClient() | |
{ | |
$mockClient = $this->getMockBuilder('Training_OrderIntegrationClient_Model_Client') | |
->setMethods( | |
array('push', 'canInvoice', 'invoice', 'canShip', 'ship', 'canRefund', 'refund') | |
) | |
->getMock(); | |
$this->setSingleton($mockClient, 'training_orderIntegrationClient/client'); | |
$this->mockClient = $mockClient; | |
} | |
protected function mockCustomerSession() | |
{ | |
$mockCustomerSession = $this->getMockBuilder('Mage_Customer_Model_Session') | |
->setMethods(array('getCustomerGroupId')) | |
->disableOriginalConstructor(true) | |
->getMock(); | |
$mockCustomerSession->expects($this->any()) | |
->method('getCustomerGroupId') | |
->will($this->returnValue(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID)); | |
$this->setSingleton($mockCustomerSession, 'customer/session'); | |
} | |
/** | |
* Create a guest quote with one item | |
* | |
* @return Mage_Sales_Model_Quote | |
*/ | |
protected function getQuoteFixture() | |
{ | |
$product = $this->getSimpleProduct(); | |
$quote = Mage::getModel('sales/quote'); | |
$quote->addProduct($product); | |
$quote->setCustomerIsGuest(true); | |
$quote->setCustomerEmail('test@example.com'); | |
$this->prepareQuoteAddress($quote->getShippingAddress()); | |
$this->prepareQuoteAddress($quote->getBillingAddress()); | |
$quote->getShippingAddress() | |
->setCollectShippingRates(true) | |
->setShippingMethod('flatrate_flatrate'); | |
$quote->getPayment()->setMethod('checkmo'); | |
$quote->collectTotals(); | |
return $quote; | |
} | |
/** | |
* Return a random active, in-stock, simple product | |
* | |
* @return Mage_Catalog_Model_Product | |
*/ | |
protected function getSimpleProduct() | |
{ | |
$collection = Mage::getModel('catalog/product') | |
->getCollection() | |
->addAttributeToSelect('*') | |
->addPriceData(Mage_Customer_Model_Group::NOT_LOGGED_IN_ID) | |
->addAttributeToFilter('type_id', 'simple') | |
->addAttributeToFilter('required_options', 0) | |
->addAttributeToFilter('status', 1) | |
->addWebsiteFilter(Mage::app()->getDefaultStoreView()->getWebsite()->getId()) | |
->setPage(1, 1); | |
Mage::getModel('cataloginventory/stock')->addInStockFilterToCollection($collection); | |
/** @var Mage_Catalog_Model_Product $product */ | |
$product = $collection->getFirstItem(); | |
return $product; | |
} | |
protected function prepareQuoteAddress(Mage_Sales_Model_Quote_Address $address) | |
{ | |
$address->addData(array( | |
'country_id' => 'US', | |
'firstname' => 'Test', | |
'lastname' => 'Test', | |
'street' => 'Test', | |
'city' => 'Test', | |
'postcode' => '123456', | |
'region' => 'CA', | |
'telephone' => '123456', | |
)); | |
} | |
protected function setCurrentCurrency() | |
{ | |
// Avoid core session being initialized | |
$store = Mage::app()->getStore(); | |
$currency = Mage::getModel('directory/currency')->load( | |
$store->getDefaultCurrencyCode() | |
); | |
$store->setCurrentCurrency($currency); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0"?> | |
<phpunit colors="true" bootstrap="bootstrap.php" strict="false"> | |
<testsuite name="Module Tests"> | |
<directory suffix="Test.php">integration/</directory> | |
</testsuite> | |
</phpunit> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment