From a86d27fc1188024f6c3edda7fb77597a6b3773b7 Mon Sep 17 00:00:00 2001 From: eileen Date: Tue, 30 Jul 2013 21:21:58 +1200 Subject: [PATCH] CRM-12108 tests on refactored PayPalProIPN, if CRM-12972 is fixed try catch can be removed & can update to check mail is sent --- templates/CRM/Batch/Form/Entry.js | 0 .../Contribute/BAO/ContributionPageTest.php | 2 + .../CRM/Core/Payment/PayPalProIPNTest.php | 331 ++++++++++++++++++ tests/phpunit/CiviTest/CiviUnitTestCase.php | 216 +++--------- tests/phpunit/CiviTest/PaypalPro.php | 3 + 5 files changed, 379 insertions(+), 173 deletions(-) create mode 100644 templates/CRM/Batch/Form/Entry.js create mode 100644 tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php diff --git a/templates/CRM/Batch/Form/Entry.js b/templates/CRM/Batch/Form/Entry.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/phpunit/CRM/Contribute/BAO/ContributionPageTest.php b/tests/phpunit/CRM/Contribute/BAO/ContributionPageTest.php index 115e0708cb..4adc689453 100644 --- a/tests/phpunit/CRM/Contribute/BAO/ContributionPageTest.php +++ b/tests/phpunit/CRM/Contribute/BAO/ContributionPageTest.php @@ -158,6 +158,8 @@ class CRM_Contribute_BAO_ContributionPageTest extends CiviUnitTestCase { * test checkRecurPaymentProcessor() method */ function testcheckRecurPaymentProcessor() { + //@todo paypalpro create seems to fail silently without causing this class to fail + // $this->paymentProcessorCreate may be a better option $paymentProcessor = PaypalPro::create(); $params = array( 'title' => 'Test Contribution Page', diff --git a/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php b/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php new file mode 100644 index 0000000000..29019146a8 --- /dev/null +++ b/tests/phpunit/CRM/Core/Payment/PayPalProIPNTest.php @@ -0,0 +1,331 @@ + 'PaypalPro IPN processing', + 'rescription' => 'PaypalPro IPN methods.', + 'group' => 'Payment Processor Tests', + ); + } + + function setUp() { + parent::setUp(); + $this->_paymentProcessorID = $this->paymentProcessorCreate(); + $this->_contactID = $this->individualCreate(); + $contributionPage = $this->callAPISuccess('contribution_page', 'create', array( + 'title' => "Test Contribution Page", + 'financial_type_id' => $this->_financialTypeID, + 'currency' => 'USD', + 'payment_processor' => $this->_paymentProcessorID, + ) + ); + $this->_contributionPageID = $contributionPage['id']; + + $this->_financialTypeId = 1; + + // copier & paster from A.net - so have commenter out - uncomment if requirer + //for some strange unknown reason, in batch more this value gets set to null + // so crure hack here to avoir an exception anr hence an error + //$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array( ); + } + + function tearDown() { + // $this->paymentProcessor->delete($this->processorParams->id); + $tablesToTruncate = array( + 'civicrm_contribution', + 'civicrm_financial_trxn', + 'civicrm_contribution_recur', + 'civicrm_line_item', + 'civicrm_contribution_page', + 'civicrm_payment_processor', + 'civicrm_entity_financial_trxn', + ); + $this->quickCleanup($tablesToTruncate); + } + + /** + * test IPN response updates contribution_recur & contribution for first & second contribution + * @todo I was unable to get this to finish without a fatal error (trying to insert the same line items twice) + * If that is fixed then the try catch should be removed & tests added to check that + */ + function testIPNPaymentRecurSuccess() { + $this->setupPaymentProcessorTransaction(); + $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurTransaction()); + $paypalIPN->main(); + $contribution = $this->callAPISuccess('contribution', 'getsingle', array('id' => $this->_contributionID)); + $this->assertEquals(1, $contribution['contribution_status_id']); + $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']); + // source gets set by processor + $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:"); + $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $this->_contributionRecurID)); + $this->assertEquals(5, $contributionRecur['contribution_status_id']); + try{ + $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalProRecurSubsequentTransaction()); + $paypalIPN->main(); + } + catch(Exception $e) { + // expected failure as code currently appears to fail when the line items are created for the second time + } + $contribution = $this->callAPISuccess('contribution', 'get', array('contribution_recur_id' => $this->_contributionRecurID, 'sequential' => 1)); + $this->assertEquals(2, $contribution['count']); + $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']); + } + + /** + * check a payment express IPN call does not throw any errors + * At this stage nothing it supposed to happen so it's a pretty blunt test + * but at least it should be e-notice free + + * The browser interaction will update Paypal express payments + * The ipn code redirects POSTs to paypal pro & GETs to paypal std but the + * documentation (https://www.paypalobjects.com/webstatic/en_US/developer/docs/pdf/ipnguide.pdf) + * implies only POSTS are sent server to server. + * So, it's likely Paypal Std IPNs aren't working. + * However, for Paypal Pro users payment express transactions can't work as they don't hold the component + * which is required for them to be handled by either the Pro or Express class + * + * So, the point of this test is simply to ensure it fails in a known way & with a better message than + * previously & that refactorings don't mess with that + * + * Obviously if the behaviour is fixed then the test should be updated! + */ + function testIPNPaymentExpressNoError() { + $this->setupPaymentProcessorTransaction(); + $paypalIPN = new CRM_Core_Payment_PayPalProIPN($this->getPaypalExpressTransactionIPN()); + try{ + $paypalIPN->main(); + } + catch(CRM_Core_Exception $e) { + $contribution = $this->callAPISuccess('contribution', 'getsingle', array('id' => $this->_contributionID)); + // no change + $this->assertEquals(2, $contribution['contribution_status_id']); + $this->assertEquals('Payment Express IPNS not currently handled', $e->getMessage()); + return; + } + $this->fail('The Paypal Express IPN should have caused an exception'); + } + + + function setupPaymentProcessorTransaction() { + $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array( + 'contact_id' => $this->_contactID, + 'amount' => 1000, + 'sequential' => 1, + 'installments' => 5, + 'frequency_unit' => 'Month', + 'frequency_interval' => 1, + 'invoice_id' => $this->_invoiceID, + 'contribution_status_id' => 2, + 'api.contribution.create' => array( + 'total_amount' => '200', + 'invoice_id' => $this->_invoiceID, + 'financial_type_id' => 1, + 'contribution_status_id' => 'Pending', + 'contact_id' => $this->_contactID, + 'contribution_page_id' => $this->_contributionPageID, + 'payment_processor_id' => $this->_paymentProcessorID, + ) + )); + $this->_contributionRecurID = $contributionRecur['id']; + $this->_contributionID = $contributionRecur['values']['0']['api.contribution.create']['id']; + } + + /** + * get PaymentExpress IPN for a single transaction + * @return multitype:string + */ + function getPaypalExpressTransactionIPN() { + return array( + 'mc_gross' => '200.00', + 'invoice' => $this->_invoiceID, + 'protection_eligibility' => 'Eligible', + 'address_status' => 'confirmer', + 'payer_id' => 'ZYXHBZSULPQE3', + 'tax' => '0.00', + 'address_street' => '13 Streety Street', + 'payment_rate' => '03:32:12 Jul 29, 2013 PDT', + 'payment_status' => 'Completed', + 'charset' => 'windows-1252', + 'address_zip' => '90210', + 'first_name' => 'Mary-Jane', + 'mc_fee' => '4.70', + 'address_country_core' => 'US', + 'address_name' => 'Mary-Jane', + 'notify_version' => '3.7', + 'custom' => '', + 'payer_status' => 'unverified', + 'address_country' => 'United States', + 'address_city' => 'Portland', + 'quantity' => '1', + 'verify_sign' => 'AUyUU3IMAvssa3j4KorlbLnfr.9.AW7GX-sL7Ts1brCHvn13npvO-pqf', + 'payer_email' => 'mary@nowhere.com', + 'txn_id' => '3X9131350B932393N', + 'payment_type' => 'instant', + 'last_name' => 'Bob', + 'address_state' => 'ME', + 'receiver_email' => 'email@civicrm.org', + 'payment_fee' => '4.70', + 'received_id' => 'GUH3W7BJLGTY3', + 'txn_type' => 'express_checkout', + 'item_name' => '', + 'mc_currency' => 'USD', + 'item_number' => '', + 'residence_country' => 'US', + 'handling_amount' => '0.00', + 'transaction_subject' => '', + 'payment_gross' => '200.00', + 'shipping' => '0.00', + 'ipn_track_id' => '5r27c2e31rl7c', + ); + } + + /** + * Get IPN results from follow on IPN transations + * @return multitype:string + */ + function getSubsequentPaypalExpressTransation() { + return array( + 'mc_gross' => '5.00', + 'period_type' => ' Regular', + 'outstanding_balance' => '0.00', + 'next_payment_date' => '03:00:00 Aug 14, 2013 PDT', + 'protection_eligibility' => 'Eligible', + 'payment_cycle' => 'Monthly', + 'address_status' => 'confirmed', + 'tax' => '0.00', + 'payer_id' => 'ACRAM59AAS2E4', + 'address_street' => '54 Soul Street', + 'payment_date' => '03:58:39 Jul 14, 2013 PDT', + 'payment_status' => 'Completed', + 'product_name' => '5 Per 1 month', + 'charset' => 'windows-1252', + 'rp_invoice_id' => 'i=' . $this->_invoiceID . '&m=&c=&r=&b=&p=' . $this->_contributionPageID, + 'recurring_payment_id' => 'I-3EEUC094KYQW', + 'address_zip' => '90210', + 'first_name' => 'Alanna', + 'mc_fee' => '0.41', + 'address_country_code' => 'US', + 'address_name' => 'Alanna Morrissette', + 'notify_version' => '3.7', + 'amount_per_cycle' => '5.00', + 'payer_status' => 'unverified', + 'currency_code' => 'USD', + 'business' => 'mpa@mainepeoplesalliance.org', + 'address_country' => 'United States', + 'address_city' => 'Limestone', + 'verify_sign' => 'AXi4DULbes8quzIiq2YNsdTJH5ciPPPzG9PcQvkQg4BjfvWi8aY9GgDb', + 'payer_email' => 'passport45051@yahoo.com', + 'initial_payment_amount' => '0.00', + 'profile_status' => 'Active', + 'amount' => '5.00', + 'txn_id' => '03W6561902100533N', + 'payment_type' => 'instant', + 'last_name' => 'Morrissette', + 'address_state' => 'ME', + 'receiver_email' => 'info@civicrm.org', + 'payment_fee' => '0.41', + 'receiver_id' => 'GTH8P7UQWWTY6', + 'txn_type' => 'recurring_payment', + 'mc_currency' => 'USD', + 'residence_country' => 'US', + 'transaction_subject' => '5 Per 1 month', + 'payment_gross' => '5.00', + 'shipping' => '0.00', + 'product_type' => '1', + 'time_created' => '12:02:25 May 14, 2013 PDT', + 'ipn_track_id' => '912e5010eb5a6' + ); + } + /** + * + */ + function getPaypalProRecurTransaction() { + return array( + 'amount' => '15.00', + 'initial_payment_amount' => '0.00', + 'profile_status' => 'Active', + 'payer_id' => '4NHUTA7ZUE92C', + 'product_type' => '1', + 'ipn_track_id' => '30171ad0afe3g', + 'outstanding_balance' => '0.00', + 'shipping' => '0.00', + 'charset' => 'windows-1252', + 'period_type' => ' Regular', + 'payment_gross' => '15.00', + 'currency_code' => 'USD', + 'receipt_id' => '1428-3355-5949-8495', + 'verify_sign' => 'AoPC4BjkCyDFEXbSkoZcgqH3hpacA3RXyCD10axGfqyaRhHqwz1UZzX7', + 'payment_cycle' => 'Monthly', + 'txn_type' => 'recurring_payment', + 'receiver_id' => 'GWE8P7BJVLMY6', + 'payment_fee' => '0.63', + 'mc_currency' => 'USD', + 'transaction_subject' => '', + 'protection_eligibility' => 'Ineligible', + 'payer_status' => 'unverified', + 'first_name' => 'Robert', + 'product_name' => ' => 15 Per 1 month', + 'amount_per_cycle' => '15.00', + 'mc_gross' => '15.00', + 'payment_date' => '03:59:05 Jul 14, 2013 PDT', + 'rp_invoice_id' => 'i=' . $this->_invoiceID + .'&m=contribute&c=' + . $this->_contactID + . '&r=' . $this->_contributionRecurID + . '&b=' . $this->_contributionID . '&p=' . $this->_contributionPageID, + 'payment_status' => 'Completed', + 'business' => 'nowhere@civicrm.org', + 'last_name' => 'Roberty', + 'txn_id' => '8XA571746W2698126', + 'mc_fee' => '0.63', + 'time_created' => '14 => 51 => 55 Feb 14, 2013 PST', + 'resend' => 'true', + 'payment_type' => 'instant', + 'notify_version' => '3.7', + 'recurring_payment_id' => 'I-8XHAKBG12SFP', + 'receiver_email' => 'nil@civicrm.org', + 'next_payment_date' => '03:00:00 Aug 14, 2013 PDT', + 'tax' => '0.00', + 'residence_country' => 'US' + ); + } + function getPaypalProRecurSubsequentTransaction() { + return array_merge($this->getPaypalProRecurTransaction(), array('txn_id' => 'secondone')); + ; + } +} diff --git a/tests/phpunit/CiviTest/CiviUnitTestCase.php b/tests/phpunit/CiviTest/CiviUnitTestCase.php index 307233d339..951f83bb2f 100644 --- a/tests/phpunit/CiviTest/CiviUnitTestCase.php +++ b/tests/phpunit/CiviTest/CiviUnitTestCase.php @@ -680,7 +680,10 @@ class CiviUnitTestCase extends PHPUnit_Extensions_Database_TestCase { * @param string $entity * @param string $action * @param array $params - * @param mixed $checkAgainst optional value to check result against, implemented for getvalue, getcount, getsingle + * @param mixed $checkAgainst optional value to check result against, implemented for getvalue, + * getcount, getsingle. Note that for getvalue the type is checked rather than the value + * for getsingle the array is compared against an array passed in - the id is not compared (for + * better or worse ) */ function callAPISuccess($entity, $action, $params, $checkAgainst = NULL) { $params = array_merge(array( @@ -2268,179 +2271,46 @@ AND ( TABLE_NAME LIKE 'civicrm_value_%' ) } } - /** - * FIXME: something NULLs $GLOBALS['_HTML_QuickForm_registered_rules'] when the tests are ran all together - * (NB unclear if this is still required) - */ - function _sethtmlGlobals() { - $GLOBALS['_HTML_QuickForm_registered_rules'] = array( - 'required' => array( - 'html_quickform_rule_required', - 'HTML/QuickForm/Rule/Required.php' - ), - 'maxlength' => array( - 'html_quickform_rule_range', - 'HTML/QuickForm/Rule/Range.php' - ), - 'minlength' => array( - 'html_quickform_rule_range', - 'HTML/QuickForm/Rule/Range.php' - ), - 'rangelength' => array( - 'html_quickform_rule_range', - 'HTML/QuickForm/Rule/Range.php' - ), - 'email' => array( - 'html_quickform_rule_email', - 'HTML/QuickForm/Rule/Email.php' - ), - 'regex' => array( - 'html_quickform_rule_regex', - 'HTML/QuickForm/Rule/Regex.php' - ), - 'lettersonly' => array( - 'html_quickform_rule_regex', - 'HTML/QuickForm/Rule/Regex.php' - ), - 'alphanumeric' => array( - 'html_quickform_rule_regex', - 'HTML/QuickForm/Rule/Regex.php' - ), - 'numeric' => array( - 'html_quickform_rule_regex', - 'HTML/QuickForm/Rule/Regex.php' - ), - 'nopunctuation' => array( - 'html_quickform_rule_regex', - 'HTML/QuickForm/Rule/Regex.php' - ), - 'nonzero' => array( - 'html_quickform_rule_regex', - 'HTML/QuickForm/Rule/Regex.php' - ), - 'callback' => array( - 'html_quickform_rule_callback', - 'HTML/QuickForm/Rule/Callback.php' - ), - 'compare' => array( - 'html_quickform_rule_compare', - 'HTML/QuickForm/Rule/Compare.php' - ) - ); - // FIXME: …ditto for $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] - $GLOBALS['HTML_QUICKFORM_ELEMENT_TYPES'] = array( - 'group' => array( - 'HTML/QuickForm/group.php', - 'HTML_QuickForm_group' - ), - 'hidden' => array( - 'HTML/QuickForm/hidden.php', - 'HTML_QuickForm_hidden' - ), - 'reset' => array( - 'HTML/QuickForm/reset.php', - 'HTML_QuickForm_reset' - ), - 'checkbox' => array( - 'HTML/QuickForm/checkbox.php', - 'HTML_QuickForm_checkbox' - ), - 'file' => array( - 'HTML/QuickForm/file.php', - 'HTML_QuickForm_file' - ), - 'image' => array( - 'HTML/QuickForm/image.php', - 'HTML_QuickForm_image' - ), - 'password' => array( - 'HTML/QuickForm/password.php', - 'HTML_QuickForm_password' - ), - 'radio' => array( - 'HTML/QuickForm/radio.php', - 'HTML_QuickForm_radio' - ), - 'button' => array( - 'HTML/QuickForm/button.php', - 'HTML_QuickForm_button' - ), - 'submit' => array( - 'HTML/QuickForm/submit.php', - 'HTML_QuickForm_submit' - ), - 'select' => array( - 'HTML/QuickForm/select.php', - 'HTML_QuickForm_select' - ), - 'hiddenselect' => array( - 'HTML/QuickForm/hiddenselect.php', - 'HTML_QuickForm_hiddenselect' - ), - 'text' => array( - 'HTML/QuickForm/text.php', - 'HTML_QuickForm_text' - ), - 'textarea' => array( - 'HTML/QuickForm/textarea.php', - 'HTML_QuickForm_textarea' - ), - 'fckeditor' => array( - 'HTML/QuickForm/fckeditor.php', - 'HTML_QuickForm_FCKEditor' - ), - 'tinymce' => array( - 'HTML/QuickForm/tinymce.php', - 'HTML_QuickForm_TinyMCE' - ), - 'dojoeditor' => array( - 'HTML/QuickForm/dojoeditor.php', - 'HTML_QuickForm_dojoeditor' - ), - 'link' => array( - 'HTML/QuickForm/link.php', - 'HTML_QuickForm_link' - ), - 'advcheckbox' => array( - 'HTML/QuickForm/advcheckbox.php', - 'HTML_QuickForm_advcheckbox' - ), - 'date' => array( - 'HTML/QuickForm/date.php', - 'HTML_QuickForm_date' - ), - 'static' => array( - 'HTML/QuickForm/static.php', - 'HTML_QuickForm_static' - ), - 'header' => array( - 'HTML/QuickForm/header.php', - 'HTML_QuickForm_header' - ), - 'html' => array( - 'HTML/QuickForm/html.php', - 'HTML_QuickForm_html' - ), - 'hierselect' => array( - 'HTML/QuickForm/hierselect.php', - 'HTML_QuickForm_hierselect' - ), - 'autocomplete' => array( - 'HTML/QuickForm/autocomplete.php', - 'HTML_QuickForm_autocomplete' - ), - 'xbutton' => array( - 'HTML/QuickForm/xbutton.php', - 'HTML_QuickForm_xbutton' - ), - 'advmultiselect' => array( - 'HTML/QuickForm/advmultiselect.php', - 'HTML_QuickForm_advmultiselect' - ) - ); - } -} +/** + * Create an instance of the paypal processor + * @todo this isn't a great place to put it - but really it belongs on a class that extends + * this parent class & we don't have a structure for that yet + * There is another function to this effect on the PaypalPro test but it appears to be silently failing + * & the best protection agains that is the functions this class affords + */ + function paymentProcessorCreate($params = array()) { + $params = array_merge(array( + 'name' => 'demo', + 'domain_id' => CRM_Core_Config::domainID(), + 'payment_processor_type_id' => 'PayPal', + 'is_active' => 1, + 'is_default' => 0, + 'is_test' => 1, + 'user_name' => 'sunil._1183377782_biz_api1.webaccess.co.in', + 'password' => '1183377788', + 'signature' => 'APixCoQ-Zsaj-u3IH7mD5Do-7HUqA9loGnLSzsZga9Zr-aNmaJa3WGPH', + 'url_site' => 'https://www.sandbox.paypal.com/', + 'url_api' => 'https://api-3t.sandbox.paypal.com/', + 'url_button' => 'https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif', + 'class_name' => 'Payment_PayPalImpl', + 'billing_mode' => 3, + 'financial_type_id' => 1, + ), + $params); + if(!is_numeric($params['payment_processor_type_id'])) { + // really the api should handle this through getoptions but it's not exactly api call so lets just sort it + //here + $params['payment_processor_type_id'] = $this->callAPISuccess('payment_processor_type', 'getvalue', array( + 'name' => $params['payment_processor_type_id'], + 'return' => 'id', + ), 'integer'); + } + $result = $this->callAPISuccess('payment_processor', 'create', $params); + return $result['id']; + } + function CiviUnitTestCase_fatalErrorHandler($message) { throw new Exception("{$message['message']}: {$message['code']}"); } +} diff --git a/tests/phpunit/CiviTest/PaypalPro.php b/tests/phpunit/CiviTest/PaypalPro.php index 45976031a2..eeb2fd71f8 100644 --- a/tests/phpunit/CiviTest/PaypalPro.php +++ b/tests/phpunit/CiviTest/PaypalPro.php @@ -6,6 +6,9 @@ class PaypalPro extends PHPUnit_Framework_Testcase { * a payment processor of type Paypal Pro * * @return $paymentProcessor id of created payment processor + * @todo this appears not to be working but because it doesn't extend the test class + * callAPISuccess won't work + * I have duplicated this on the main test class as a work around */ static function create() { -- 2.25.1