<?php

/**
 * @file
 * Shopping cart and checkout tests.
 */

// Ensure UbercartTestHelper is available.
module_load_include('test', 'uc_store', 'uc_store');

/**
 * Tests the cart and checkout functionality.
 */
class UbercartCartCheckoutTestCase extends UbercartTestHelper {

  public static function getInfo() {
    return array(
      'name' => 'Cart and checkout',
      'description' => 'Ensures the cart and checkout process is functioning for both anonymous and authenticated users.',
      'group' => 'Ubercart',
    );
  }

  function setUp() {
    $modules = array('uc_payment', 'uc_payment_pack', 'uc_roles');
    $permissions = array('administer permissions', 'administer product features');
    parent::setUp($modules, $permissions);
  }

  /**
   * Creates a new order.
   */
  function createOrder($fields = array()) {
    $order = uc_order_new();
    foreach ($fields as $key => $value) {
      $order->$key = $value;
    }

    if (empty($order->primary_email)) {
      $order->primary_email = $this->randomString() .'@example.org';
    }

    if (!isset($fields['products'])) {
      $item = clone $this->product;
      $item->qty = 1;
      $item->price = $item->sell_price;
      $item->data = array();
      $order->products = array($item);
    }

    $order->order_total = uc_order_get_total($order, TRUE);
    $order->line_items = uc_order_load_line_items($order, TRUE);
    uc_order_save($order);

    return $order;
  }

  function testCart() {
    // Test the empty cart text.
    $this->drupalGet('cart');
    $this->assertText('There are no products in your shopping cart.');

    // Add an item to the cart as an anonymous user.
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->assertText($this->product->title . ' added to your shopping cart.');

    // Log in and check the item is still in the cart.
    $this->drupalLogin($this->customer);
    $this->drupalGet('cart');
    $this->assertText($this->product->title, t('The product remains in the cart after logging in.'));
    $this->assertFieldByName('items[0][qty]', 1, t('The product quantity is 1.'));

    // Update the quantity.
    $qty = mt_rand(2, 100);
    $this->drupalPost('cart', array('items[0][qty]' => $qty), t('Update cart'));
    $this->assertText('Your cart has been updated.');
    $this->assertFieldByName('items[0][qty]', $qty, t('The product quantity was updated.'));

    // Test the remove item button.
    $this->drupalPost('cart', array(), t('Remove'));
    $this->assertText($this->product->title . ' removed from your shopping cart.');
    $this->assertText('There are no products in your shopping cart.');
    $this->drupalLogout();
  }

  function testDeletedCartItem() {
    // Add a product to the cart, then delete the node.
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    node_delete($this->product->nid);

    // Test that the cart is empty.
    $this->drupalGet('cart');
    $this->assertText('There are no products in your shopping cart.');
    $this->assertEqual(uc_cart_get_total_qty(), 0, 'There are no items in the cart.');
  }

  function testCheckout() {
    // Allow customer to specify username and password, but don't log in after checkout.
    $settings = array(
      'uc_cart_new_account_name' => TRUE,
      'uc_cart_new_account_password' => TRUE,
    );
    $this->drupalLogin($this->adminUser);
    $this->drupalPost('admin/store/settings/checkout/edit', array('uc_new_customer_login' => FALSE), t('Save configuration'));
    $this->drupalPost('admin/store/settings/checkout/edit/panes', $settings, t('Save configuration'));
    $this->drupalLogout();

    $new_user = new stdClass;
    $new_user->name = $this->randomName(20);
    $new_user->pass_raw = $this->randomName(20);

    // Test as anonymous user.
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->checkout(array(
      'panes[customer][new_account][name]' => $new_user->name,
      'panes[customer][new_account][pass]' => $new_user->pass_raw,
      'panes[customer][new_account][pass_confirm]' => $new_user->pass_raw,
    ));
    $this->assertRaw('Your order is complete!');
    $this->assertText($new_user->name, 'Username is shown on screen.');
    $this->assertNoText($new_user->pass_raw, 'Password is not shown on screen.');

    // Check that cart is now empty.
    $this->drupalGet('cart');
    $this->assertText('There are no products in your shopping cart.');

    // New account email is last but two.
    $mail = array_pop(array_slice($this->drupalGetMails(), -3, 1));
    $this->assertTrue(strpos($mail['body'], $new_user->name) !== FALSE, 'Mail body contains username.');

    // Invoice email is last.
    $mail = array_pop(array_slice($this->drupalGetMails(), -1, 1));
    $this->assertTrue(strpos($mail['body'], $new_user->name) !== FALSE, 'Invoice body contains username.');
    $this->assertFalse(strpos($mail['body'], $new_user->pass_raw) !== FALSE, 'Invoice body does not contain password.');

    // Check that the password works.
    $this->drupalLogout();
    $this->drupalLogin($new_user);

    // Test again as authenticated user.
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->checkout();
    $this->assertRaw('Your order is complete!');
    $this->assertRaw('While logged in');

    // Test again with generated username and password.
    $this->drupalLogout();
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->checkout();
    $this->assertRaw('Your order is complete!');

    // New account email is last but two.
    $mail = array_pop(array_slice($this->drupalGetMails(), -3, 1));

    $new_user = new stdClass;
    $new_user->name = $mail['params']['account']->name;
    $new_user->pass_raw = $mail['params']['account']->password;
    $this->assertTrue(!empty($new_user->name), 'New username is not empty.');
    $this->assertTrue(!empty($new_user->pass_raw), 'New password is not empty.');
    $this->assertTrue(strpos($mail['body'], $new_user->name) !== FALSE, 'Mail body contains username.');
    $this->assertTrue(strpos($mail['body'], $new_user->pass_raw) !== FALSE, 'Mail body contains password.');

    // Invoice email is last.
    $mail = array_pop(array_slice($this->drupalGetMails(), -1, 1));
    $this->assertTrue(strpos($mail['body'], $new_user->name) !== FALSE, 'Invoice body contains username.');
    $this->assertTrue(strpos($mail['body'], $new_user->pass_raw) !== FALSE, 'Invoice body contains password.');

    // We can check the password now we know it.
    $this->assertText($new_user->name, 'Username is shown on screen.');
    $this->assertText($new_user->pass_raw, 'Password is shown on screen.');

    // Check that the password works.
    $this->drupalLogout();
    $this->drupalLogin($new_user);

    // Test again with an existing email address
    $this->drupalLogout();
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->checkout(array('panes[customer][primary_email]' => $this->customer->mail));
    $this->assertRaw('Your order is complete!');
    $this->assertRaw('order has been attached to the account we found');
  }

  function testCheckoutLogin() {
    // Log in after checkout.
    $settings = array(
      'uc_new_customer_login' => TRUE,
    );
    $this->drupalLogin($this->adminUser);
    $this->drupalPost('admin/store/settings/checkout/edit', $settings, t('Save configuration'));
    $this->drupalLogout();

    // Test checkout.
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->checkout();
    $this->assertRaw('Your order is complete!');
    $this->assertRaw('you are already logged in');

    // Confirm login.
    $this->drupalGet('<front>');
    $this->assertText('My account', 'User is logged in.');

    // Check that cart is now empty.
    $this->drupalGet('cart');
    $this->assertText('There are no products in your shopping cart.');
  }

  function testCheckoutComplete() {
    // Payment notification is received first.
    $order_data = array('primary_email' => 'simpletest@ubercart.org');
    $order = $this->createOrder($order_data);
    uc_payment_enter($order->order_id, 'SimpleTest', $order->order_total);
    uc_cart_complete_sale($order);

    // Check that a new account was created.
    $this->assertTrue($order->data['complete_sale'] == 'new_user', 'A new account was created.');

    // 3 e-mails: new account, customer invoice, admin invoice
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 3, '3 e-mails were sent.');
    variable_del('drupal_test_email_collector');

    $password = $mails[0]['params']['account']->password;
    $this->assertTrue(!empty($password), 'New password is not empty.');
    $this->assertTrue(strpos($mails[0]['body'], $password) !== FALSE, 'Mail body contains password.');

    // Different user, sees the checkout page first.
    $order_data = array('primary_email' => 'simpletest2@ubercart.org');
    $order = $this->createOrder($order_data);
    uc_cart_complete_sale($order, TRUE);
    uc_payment_enter($order->order_id, 'SimpleTest', $order->order_total);

    // Check that a new account was created.
    $this->assertTrue($order->data['complete_sale'] == 'new_user', 'A new account was created.');

    // 3 e-mails: new account, customer invoice, admin invoice
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 3, '3 e-mails were sent.');
    variable_del('drupal_test_email_collector');

    $password = $mails[0]['params']['account']->password;
    $this->assertTrue(!empty($password), 'New password is not empty.');
    $this->assertTrue(strpos($mails[0]['body'], $password) !== FALSE, 'Mail body contains password.');

    // Same user, new order.
    $order = $this->createOrder($order_data);
    uc_cart_complete_sale($order, TRUE);
    uc_payment_enter($order->order_id, 'SimpleTest', $order->order_total);

    // Check that no new account was created.
    $this->assertTrue($order->data['complete_sale'] == 'existing_user', 'The order was attached to an existing account.');

    // 2 e-mails: customer invoice, admin invoice
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 2, '2 e-mails were sent.');
    variable_del('drupal_test_email_collector');
  }

  function testCheckoutRoleAssignment() {
    // Add role assignment to the test product.
    $rid = $this->drupalCreateRole(array('access content'));
    $this->drupalLogin($this->adminUser);
    $this->drupalPost('node/'. $this->product->nid .'/edit/features', array('feature' => 'role'), t('Add'));
    $this->drupalPost(NULL, array('uc_roles_role' => $rid), t('Save feature'));

    // Process an anonymous, shippable order.
    $item = clone $this->product;
    $item->qty = 1;
    $item->price = $item->sell_price;
    $item->data = array('shippable' => TRUE);
    $order = $this->createOrder(array(
      'products' => array($item),
    ));
    uc_payment_enter($order->order_id, 'SimpleTest', $order->order_total);

    // Find the order uid.
    $uid = db_result(db_query("SELECT uid FROM {uc_orders} ORDER BY order_id DESC"));
    $account = user_load($uid);
    $this->assertTrue(isset($account->roles[$rid]), 'New user was granted role.');
    $order = uc_order_load($order->order_id);
    $this->assertEqual($order->order_status, 'payment_received', 'Shippable order was set to payment received.');

    // 4 e-mails: new account, customer invoice, admin invoice, role assignment
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 4, '4 e-mails were sent.');
    variable_del('drupal_test_email_collector');

    // Test again with an existing email address and a non-shippable order.
    $item->data = array('shippable' => FALSE);
    $order = $this->createOrder(array(
      'primary_email' => $this->customer->mail,
      'products' => array($item),
    ));
    uc_payment_enter($order->order_id, 'SimpleTest', $order->order_total);
    $account = user_load($this->customer->uid);
    $this->assertTrue(isset($account->roles[$rid]), 'Existing user was granted role.');
    $order = uc_order_load($order->order_id);
    $this->assertEqual($order->order_status, 'completed', 'Non-shippable order was set to completed.');

    // 3 e-mails: customer invoice, admin invoice, role assignment
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 3, '3 e-mails were sent.');
    variable_del('drupal_test_email_collector');
  }
}

/**
 * Tests the cart settings page.
 */
class UbercartCartSettingsTestCase extends UbercartTestHelper {

  public static function getInfo() {
    return array(
      'name' => 'Cart settings',
      'description' => 'Tests the cart settings page.',
      'group' => 'Ubercart',
    );
  }

  function testAddToCartRedirect() {
    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/store/settings/cart/edit');
    $this->assertField('uc_add_item_redirect', t('Add to cart redirect field exists'));

    $redirect = $this->randomName(8);
    $this->drupalPost('admin/store/settings/cart/edit', array('uc_add_item_redirect' => $redirect), t('Save configuration'));

    $this->drupalPost('node/'. $this->product->nid, array(), t('Add to cart'));
    $url_pass = ($this->getUrl() == url($redirect, array('absolute' => TRUE)));
    $this->assertTrue($url_pass, t('Add to cart redirect takes user to the correct URL.'));
  }

  function testMinimumSubtotal() {
    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/store/settings/cart/edit');
    $this->assertField('uc_minimum_subtotal', t('Minimum order subtotal field exists'));

    $minimum_subtotal = mt_rand(2, 9999);
    $this->drupalPost(NULL, array('uc_minimum_subtotal' => $minimum_subtotal), t('Save configuration'));

    // Create two products, one below the minimum price, and one above the minimum price.
    $product_below_limit = $this->createProduct(array('sell_price' => $minimum_subtotal - 1));
    $product_above_limit = $this->createProduct(array('sell_price' => $minimum_subtotal + 1));
    $this->drupalLogout();

    // Check to see if the lower priced product triggers the minimum price logic.
    $this->drupalPost('node/' . $product_below_limit->nid, array(), t('Add to cart'));
    $this->drupalPost('cart', array(), t('Checkout'));
    $this->assertRaw('The minimum order subtotal for checkout is', t('Prevented checkout below the minimum order total.'));

    // Add another product to the cart, and verify that we land on the checkout page.
    $this->drupalPost('node/' . $product_above_limit->nid, array(), t('Add to cart'));
    $this->drupalPost('cart', array(), t('Checkout'));
    $this->assertText('Enter your billing address and information here.');
  }

  function testContinueShopping() {
    // Continue shopping link should take you back to the product page.
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->assertLink(t('Continue shopping'), 0, t('Continue shopping link appears on the page.'));
    $links = $this->xpath('//a[@href="' . url('node/' . $this->product->nid, array('absolute' => FALSE)) . '"]');
    $this->assertTrue(isset($links[0]), t('Continue shopping link returns to the product page.'));

    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/store/settings/cart/edit');
    $this->assertField('uc_continue_shopping_type', t('Continue shopping element display field exists'));
    $this->assertField('uc_continue_shopping_url', t('Default continue shopping link URL field exists'));
    $this->assertField('uc_continue_shopping_text', t('Custom continue shopping link text field exists'));

    // Test continue shopping button that sends users to a fixed URL.
    $settings = array(
      'uc_continue_shopping_type' => 'button',
      'uc_continue_shopping_use_last_url' => FALSE,
      'uc_continue_shopping_url' => $this->randomName(8),
      'uc_continue_shopping_text' => $this->randomName(16),
    );
    $this->drupalPost(NULL, $settings, t('Save configuration'));

    $this->drupalPost('cart', array(), $settings['uc_continue_shopping_text']);
    $url_pass = ($this->getUrl() == url($settings['uc_continue_shopping_url'], array('absolute' => TRUE)));
    $this->assertTrue($url_pass, t('Continue shopping button is properly labelled, and takes the user to the correct URL.'));
  }

  function testCartBreadcrumb() {
    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/store/settings/cart/edit');
    $this->assertField('uc_cart_breadcrumb_text', t('Custom cart breadcrumb text field exists'));
    $this->assertField('uc_cart_breadcrumb_url',t('Custom cart breadcrumb URL'));

    $settings = array(
      'uc_cart_breadcrumb_text' => $this->randomName(8),
      'uc_cart_breadcrumb_url' => $this->randomName(7),
    );
    $this->drupalPost(NULL, $settings, t('Save configuration'));

    $this->drupalPost('node/'. $this->product->nid, array(), t('Add to cart'));
    $this->assertLink($settings['uc_cart_breadcrumb_text'], 0, t('The breadcrumb link text is set correctly.'));
    $links = $this->xpath('//a[@href="' . url($settings['uc_cart_breadcrumb_url']) . '"]');
    $this->assertTrue(isset($links[0]), t('The breadcrumb link is set correctly.'));
  }
}

/**
 * Tests the checkout settings page.
 */
class UbercartCheckoutSettingsTestCase extends UbercartTestHelper {

  public static function getInfo() {
    return array(
      'name' => 'Checkout settings',
      'description' => 'Tests the checkout settings page.',
      'group' => 'Ubercart',
    );
  }

  function testEnableCheckout() {
    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/store/settings/checkout/edit');
    $this->assertField('uc_checkout_enabled', t('Enable checkout field exists'));

    $this->drupalPost('admin/store/settings/checkout/edit', array('uc_checkout_enabled' => FALSE), t('Save configuration'));

    $this->drupalPost('node/'. $this->product->nid, array(), t('Add to cart'));
    $this->assertNoRaw(t('Checkout'));
    $buttons = $this->xpath('//input[@value="' . t('Checkout') . '"]');
    $this->assertFalse(isset($buttons[0]), t('The checkout button is not shown.'));
  }

  function testAnonymousCheckout() {
    $this->drupalLogin($this->adminUser);
    $this->drupalGet('admin/store/settings/checkout/edit');
    $this->assertField('uc_checkout_anonymous', t('Anonymous checkout field exists'));

    $this->drupalPost('admin/store/settings/checkout/edit', array('uc_checkout_anonymous' => FALSE), t('Save configuration'));

    $this->drupalLogout();
    $this->drupalPost('node/'. $this->product->nid, array(), t('Add to cart'));
    $this->drupalPost('cart', array(), 'Checkout');
    $this->assertNoText('Enter your billing address and information here.', t('The checkout page is not displayed.'));
  }
}
