uc_store.test 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <?php
  2. /**
  3. * @file
  4. * Test functionality provided by uc_store.
  5. */
  6. /**
  7. * Defines a base helper class for Ubercart tests.
  8. */
  9. class UbercartTestHelper extends DrupalWebTestCase {
  10. /** User with privileges to do everything. */
  11. protected $adminUser;
  12. /** Authenticated but unprivileged user. */
  13. protected $customer;
  14. /** Test product. */
  15. protected $product;
  16. /**
  17. * Overrides DrupalWebTestCase::setUp().
  18. *
  19. * Configures basic Ubercart store components.
  20. *
  21. * @param $modules
  22. * Optional list of extra modules to install.
  23. * @param $permissions
  24. * Optional list of extra permissions for $this->adminUser.
  25. */
  26. function setUp($modules = array(), $permissions = array()) {
  27. // Enable the core Ubercart modules and dependencies, along with any other modules passed as arguments.
  28. $modules = array_merge(array('uc_store', 'rules', 'uc_order', 'uc_product', 'uc_cart'), $modules);
  29. call_user_func_array(array('parent', 'setUp'), $modules);
  30. // Create a store administrator user account.
  31. $this->adminUser = $this->drupalCreateUser(array_merge($permissions, array(
  32. 'administer store',
  33. 'administer order workflow',
  34. 'administer product classes',
  35. 'administer product features',
  36. 'administer products',
  37. 'create product content',
  38. 'delete any product content',
  39. 'edit any product content',
  40. 'create orders',
  41. 'view all orders',
  42. 'edit orders',
  43. 'delete orders',
  44. 'unconditionally delete orders',
  45. )));
  46. // Create a simple customer user account.
  47. $this->customer = $this->drupalCreateUser(array('view own orders'));
  48. // Create a test product.
  49. $this->product = $this->createProduct(array('uid' => $this->adminUser->uid));
  50. }
  51. /**
  52. * Creates a new product.
  53. */
  54. function createProduct($product = array()) {
  55. // Set the default required fields.
  56. $weight_units = array('lb', 'kg', 'oz', 'g');
  57. $length_units = array('in', 'ft', 'cm', 'mm');
  58. $product += array(
  59. 'type' => 'product',
  60. 'model' => $this->randomName(8),
  61. 'list_price' => mt_rand(1, 9999),
  62. 'cost' => mt_rand(1, 9999),
  63. 'sell_price' => mt_rand(1, 9999),
  64. 'weight' => mt_rand(1, 9999),
  65. 'weight_units' => array_rand(array_flip($weight_units)),
  66. 'length' => mt_rand(1, 9999),
  67. 'width' => mt_rand(1, 9999),
  68. 'height' => mt_rand(1, 9999),
  69. 'length_units' => array_rand(array_flip($length_units)),
  70. 'pkg_qty' => mt_rand(1, 99),
  71. 'default_qty' => 1,
  72. 'ordering' => mt_rand(-25, 25),
  73. 'shippable' => TRUE,
  74. );
  75. return $this->drupalCreateNode($product);
  76. }
  77. /**
  78. * Creates a new product class.
  79. *
  80. * Fix this after adding a proper API call for saving a product class.
  81. */
  82. function createProductClass($data = array()) {
  83. $product_class = $data + array(
  84. 'pcid' => $this->randomName(8),
  85. 'name' => $this->randomName(8),
  86. 'description' => $this->randomName(8),
  87. );
  88. $product_class = (object) $product_class;
  89. drupal_write_record('uc_product_classes', $product_class);
  90. return $product_class;
  91. }
  92. /**
  93. * Helper function to fill-in required fields on the checkout page.
  94. *
  95. * @param $edit
  96. * The form-values array to which to add required fields.
  97. */
  98. function populateCheckoutForm($edit = array()) {
  99. foreach (array('billing', 'delivery') as $pane) {
  100. $prefix = 'panes[' . $pane . '][' . $pane;
  101. $key = $prefix . '_country]';
  102. $country = empty($edit[$key]) ? variable_get('uc_store_country', 840) : $edit[$key];
  103. $zone_id = db_query_range('SELECT zone_id FROM {uc_zones} WHERE zone_country_id = :country ORDER BY rand()', 0, 1, array('country' => $country))->fetchField();
  104. $edit += array(
  105. $prefix . '_first_name]' => $this->randomName(10),
  106. $prefix . '_last_name]' => $this->randomName(10),
  107. $prefix . '_street1]' => $this->randomName(10),
  108. $prefix . '_city]' => $this->randomName(10),
  109. $prefix . '_zone]' => $zone_id,
  110. $prefix . '_postal_code]' => mt_rand(10000, 99999),
  111. );
  112. }
  113. // If the email address has not been set, and the user has not logged in,
  114. // add a primary email address.
  115. if (!isset($edit['panes[customer][primary_email]']) && !$this->loggedInUser) {
  116. $edit['panes[customer][primary_email]'] = $this->randomName(8) . '@example.com';
  117. }
  118. return $edit;
  119. }
  120. /**
  121. * Executes the checkout process.
  122. */
  123. function checkout($edit = array()) {
  124. $this->drupalPost('cart', array(), 'Checkout');
  125. $this->assertText(
  126. t('Enter your billing address and information here.'),
  127. t('Viewed cart page: Billing pane has been displayed.')
  128. );
  129. $edit = $this->populateCheckoutForm($edit);
  130. // Submit the checkout page.
  131. $this->drupalPost('cart/checkout', $edit, t('Review order'));
  132. $this->assertRaw(t('Your order is almost complete.'));
  133. // Complete the review page.
  134. $this->drupalPost(NULL, array(), t('Submit order'));
  135. $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField();
  136. if ($order_id) {
  137. $this->pass(
  138. t('Order %order_id has been created', array('%order_id' => $order_id))
  139. );
  140. $order = uc_order_load($order_id);
  141. }
  142. else {
  143. $this->fail(t('No order was created.'));
  144. $order = FALSE;
  145. }
  146. return $order;
  147. }
  148. /**
  149. * Assert that an email was sent with a specific subject line.
  150. *
  151. * @param $pattern
  152. * A regular expression to match the subject against.
  153. *
  154. * @return
  155. * An array containing the most recently sent matching email,
  156. * or FALSE if the subject line did not match anything.
  157. */
  158. function findMail($pattern) {
  159. foreach (array_reverse($this->drupalGetMails()) as $mail) {
  160. if (preg_match($pattern, $mail['subject'])) {
  161. $this->pass(t('E-mail found with subject matching %pattern.', array('%pattern' => $pattern)));
  162. return $mail;
  163. }
  164. }
  165. $this->fail(t('E-mail not found with subject matching %pattern.', array('%pattern' => $pattern)));
  166. return FALSE;
  167. }
  168. /**
  169. * Helper function to test for text in a drupal ajax response.
  170. *
  171. * @param $ajax
  172. * The ajax response to test. Must be an array of ajax commands as returned by drupalPostAjax().
  173. * @param $text
  174. * The text to search for.
  175. * @param $message
  176. * The assertion message.
  177. * @param $not_exists
  178. * TRUE to assert that the text is not present. FALSE (the default) to assert that it is present.
  179. * @param $plain
  180. * TRUE to check only the plain-text contents of the 'data' keys of each 'insert' command (i.e. what would
  181. * be inserted into the page). FALSE to check the complete, json-encoded ajax response.
  182. */
  183. function assertAjaxHelper($ajax, $text, $message = FALSE, $not_exists = FALSE, $plain = TRUE) {
  184. $content = '';
  185. if ($plain) {
  186. foreach ($ajax as $command) {
  187. if ($command['command'] == 'insert' && !empty($command['data']) && is_string($command['data'])) {
  188. $content .= $command['data'];
  189. }
  190. }
  191. $content = filter_xss($content, array());
  192. }
  193. else {
  194. $content = drupal_json_encode($ajax);
  195. }
  196. if (!$message) {
  197. $message = !$not_exists ? t('"@text" found in ajax response', array('@text' => $text)) : t('"@text" not found in ajax response', array('@text' => $text));
  198. }
  199. $this->assert($not_exists == (strpos($content, $text) === FALSE), $message);
  200. }
  201. /**
  202. * Assert that the specified text is present in the plain text version of the html that would
  203. * be inserted into the page if this ajax response were executed.
  204. *
  205. * @param $ajax
  206. * The ajax response to test. Must be an array of ajax commands as returned by drupalPostAjax().
  207. * @param $text
  208. * The text to search for.
  209. * @param $message
  210. * The assertion message.
  211. */
  212. function assertAjaxText($ajax, $text, $message = FALSE) {
  213. $this->assertAjaxHelper($ajax, $text, $message, FALSE, TRUE);
  214. }
  215. /**
  216. * Assert that the specified text is not present in the plain text version of the html that would
  217. * be inserted into the page if this ajax response were executed.
  218. *
  219. * @param $ajax
  220. * The ajax response to test. Must be an array of ajax commands as returned by drupalPostAjax().
  221. * @param $text
  222. * The text to search for.
  223. * @param $message
  224. * The assertion message.
  225. */
  226. function assertNoAjaxText($ajax, $text, $message = FALSE) {
  227. $this->assertAjaxHelper($ajax, $text, $message, TRUE, TRUE);
  228. }
  229. /**
  230. * Assert that the specified text is present in the raw drupal ajax response.
  231. *
  232. * @param $ajax
  233. * The ajax response to test. Must be an array of ajax commands as returned by drupalPostAjax().
  234. * @param $text
  235. * The text to search for.
  236. * @param $message
  237. * The assertion message.
  238. */
  239. function assertAjaxRaw($ajax, $text, $message = FALSE) {
  240. $this>assertAjaxHelper($ajax, $text, $message, FALSE, FALSE);
  241. }
  242. /**
  243. * Assert that the specified text is not present in the raw drupal ajax response.
  244. *
  245. * @param $ajax
  246. * The ajax response to test. Must be an array of ajax commands as returned by drupalPostAjax().
  247. * @param $text
  248. * The text to search for.
  249. * @param $message
  250. * The assertion message.
  251. */
  252. function assertNoAjaxRaw($ajax, $text, $message = FALSE) {
  253. $this>assertAjaxHelper($ajax, $text, $message, TRUE, FALSE);
  254. }
  255. /**
  256. * Extends drupalPostAjax() to replace additional content on the page after an ajax submission.
  257. *
  258. * DrupalWebTestCase::drupalPostAjax() will only process ajax insertions which don't have a 'selector' attribute,
  259. * because it's not easy to convert from a jQuery selector to an XPath. However, ubercart uses many simple,
  260. * id-based selectors, and these can be converted easily (eg: '#my-identifier' => '//*[@id="my-identifier"]').
  261. * This helper method post-processes the command array returned by drupalPostAjax() to perform these insertions.
  262. *
  263. * @see DrupalWebTestCase::drupalPostAjax()
  264. */
  265. protected function ucPostAJAX($path, $edit, $triggering_element, $ajax_path = NULL, array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = NULL) {
  266. $commands = parent::drupalPostAJAX($path, $edit, $triggering_element, $ajax_path, $options, $headers, $form_html_id, $ajax_settings);
  267. $dom = new DOMDocument();
  268. @$dom->loadHTML($this->drupalGetContent());
  269. foreach ($commands as $command) {
  270. if ($command['command'] == 'insert' && isset($command['selector']) && preg_match('/^\#-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/', $command['selector'])) {
  271. $xpath = new DOMXPath($dom);
  272. $wrapperNode = $xpath->query('//*[@id="' . substr($command['selector'], 1) . '"]')->item(0);
  273. if ($wrapperNode) {
  274. // ajax.js adds an enclosing DIV to work around a Safari bug.
  275. $newDom = new DOMDocument();
  276. $newDom->loadHTML('<div>' . $command['data'] . '</div>');
  277. $newNode = $dom->importNode($newDom->documentElement->firstChild->firstChild, TRUE);
  278. $method = isset($command['method']) ? $command['method'] : $ajax_settings['method'];
  279. // The "method" is a jQuery DOM manipulation function. Emulate
  280. // each one using PHP's DOMNode API.
  281. switch ($method) {
  282. case 'replaceWith':
  283. $wrapperNode->parentNode->replaceChild($newNode, $wrapperNode);
  284. break;
  285. case 'append':
  286. $wrapperNode->appendChild($newNode);
  287. break;
  288. case 'prepend':
  289. // If no firstChild, insertBefore() falls back to
  290. // appendChild().
  291. $wrapperNode->insertBefore($newNode, $wrapperNode->firstChild);
  292. break;
  293. case 'before':
  294. $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode);
  295. break;
  296. case 'after':
  297. // If no nextSibling, insertBefore() falls back to
  298. // appendChild().
  299. $wrapperNode->parentNode->insertBefore($newNode, $wrapperNode->nextSibling);
  300. break;
  301. case 'html':
  302. foreach ($wrapperNode->childNodes as $childNode) {
  303. $wrapperNode->removeChild($childNode);
  304. }
  305. $wrapperNode->appendChild($newNode);
  306. break;
  307. }
  308. }
  309. }
  310. }
  311. $content = $dom->saveHTML();
  312. $this->drupalSetContent($content);
  313. $this->verbose('Page content after ajax submission:<hr />' . $this->content);
  314. return $commands;
  315. }
  316. }
  317. /**
  318. * Test the country import and update functions.
  319. */
  320. class UbercartCountryTestCase extends UbercartTestHelper {
  321. public static function getInfo() {
  322. return array(
  323. 'name' => 'Country functionality',
  324. 'description' => 'Import, edit, and remove countries and their settings.',
  325. 'group' => 'Ubercart',
  326. );
  327. }
  328. /**
  329. * Test import/enable/disable/remove of Country information files.
  330. */
  331. function testCountries() {
  332. $import_file = 'belgium_56_3.cif';
  333. $country_name = 'Belgium';
  334. $country_code = 'BEL';
  335. $this->drupalLogin($this->adminUser);
  336. $this->drupalGet('admin/store/settings/countries');
  337. $this->assertRaw(
  338. '<option value="' . $import_file . '">' . $import_file . '</option>',
  339. t('Ensure country file is not imported yet.')
  340. );
  341. $edit = array(
  342. 'import_file[]' => array($import_file => $import_file),
  343. );
  344. $this->drupalPost(
  345. 'admin/store/settings/countries',
  346. $edit,
  347. t('Import')
  348. );
  349. $this->assertText(
  350. t('Country file @file imported.', array('@file' => $import_file)),
  351. t('Country was imported successfully.')
  352. );
  353. $this->assertText(
  354. $country_code,
  355. t('Country appears in the imported countries table.')
  356. );
  357. $this->assertNoRaw(
  358. '<option value="' . $import_file . '">' . $import_file . '</option>',
  359. t('Country does not appear in list of files to be imported.')
  360. );
  361. // Have to pick the right one here!
  362. $this->clickLink(t('disable'));
  363. $this->assertText(
  364. t('@name disabled.', array('@name' => $country_name)),
  365. t('Country was disabled.')
  366. );
  367. $this->clickLink(t('enable'));
  368. $this->assertText(
  369. t('@name enabled.', array('@name' => $country_name)),
  370. t('Country was enabled.')
  371. );
  372. $this->clickLink(t('remove'));
  373. $this->assertText(
  374. t('Are you sure you want to remove @name from the system?', array('@name' => $country_name)),
  375. t('Confirm form is displayed.')
  376. );
  377. $this->drupalPost(
  378. 'admin/store/settings/countries/56/remove',
  379. array(),
  380. t('Remove')
  381. );
  382. $this->assertText(
  383. t('@name removed.', array('@name' => $country_name)),
  384. t('Country removed.')
  385. );
  386. $this->assertRaw(
  387. '<option value="' . $import_file . '">' . $import_file . '</option>',
  388. t('Ensure country file is not imported yet.')
  389. );
  390. $this->assertNoText(
  391. $country_code,
  392. t('Country does not appear in imported countries table.')
  393. );
  394. }
  395. }