<?php

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

/**
 * 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');
    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,
      'uc_new_customer_login' => FALSE,
    );
    $this->drupalLogin($this->adminUser);
    $this->drupalPost('admin/store/settings/checkout', $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 one.
    $mail = array_pop(array_slice($this->drupalGetMails(), -2, 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, 'Mail 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 one.
    $mail = array_pop(array_slice($this->drupalGetMails(), -2, 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.');

    // 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', $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);
    $output = uc_cart_complete_sale($order);

    // Check that a new account was created.
    $this->assertTrue(strpos($output['#message'], 'new account has been created') !== FALSE, 'Checkout message mentions new account.');

    // 2 e-mails: new account, customer invoice
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 2, '2 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.');
    // In D7, new account emails do not contain the password.
    //$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);
    $output = uc_cart_complete_sale($order, TRUE);
    uc_payment_enter($order->order_id, 'SimpleTest', $order->order_total);

    // 2 e-mails: new account, customer invoice
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 2, '2 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.');
    // In D7, new account emails do not contain the password.
    //$this->assertTrue(strpos($mails[0]['body'], $password) !== FALSE, 'Mail body contains password.');

    // Same user, new order.
    $order = $this->createOrder($order_data);
    $output = 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(strpos($output['#message'], 'order has been attached to the account') !== FALSE, 'Checkout message mentions existing account.');

    // 1 e-mail: customer invoice
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 1, '1 e-mail was 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_query("SELECT uid FROM {uc_orders} ORDER BY order_id DESC")->fetchField();
    $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.');

    // 3 e-mails: new account, customer invoice, role assignment
    $mails = $this->drupalGetMails();
    $this->assertEqual(count($mails), 3, '3 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.');

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

  /**
   * Tests that cart orders are marked abandoned after a timeout.
   */
  function testCartOrderTimeout() {
    $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
    $this->drupalPost('cart', array(), 'Checkout');
    $this->assertText(
      t('Enter your billing address and information here.'),
      t('Viewed cart page: Billing pane has been displayed.')
    );

    // Build the panes.
    $zone_id = db_query_range('SELECT zone_id FROM {uc_zones} WHERE zone_country_id = :country ORDER BY rand()', 0, 1, array('country' => variable_get('uc_store_country', 840)))->fetchField();
    $oldname = $this->randomName(10);
    $edit = array(
      'panes[delivery][delivery_first_name]' => $oldname,
      'panes[delivery][delivery_last_name]' => $this->randomName(10),
      'panes[delivery][delivery_street1]' => $this->randomName(10),
      'panes[delivery][delivery_city]' => $this->randomName(10),
      'panes[delivery][delivery_zone]' => $zone_id,
      'panes[delivery][delivery_postal_code]' => mt_rand(10000, 99999),

      'panes[billing][billing_first_name]' => $this->randomName(10),
      'panes[billing][billing_last_name]' => $this->randomName(10),
      'panes[billing][billing_street1]' => $this->randomName(10),
      'panes[billing][billing_city]' => $this->randomName(10),
      'panes[billing][billing_zone]' => $zone_id,
      'panes[billing][billing_postal_code]' => mt_rand(10000, 99999),
    );

    // If the email address has not been set, and the user has not logged in,
    // add a primary email address.
    if (!isset($edit['panes[customer][primary_email]']) && !$this->loggedInUser) {
      $edit['panes[customer][primary_email]'] = $this->randomName(8) . '@example.com';
    }

    // Submit the checkout page.
    $this->drupalPost('cart/checkout', $edit, t('Review order'));

    $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $oldname))->fetchField();
    if ($order_id) {
      // Go to a different page, then back to order - make sure order_id is the same.
      $this->drupalGet('<front>');
      $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
      $this->drupalPost('cart', array(), 'Checkout');
      $this->assertRaw($oldname, 'Customer name was unchanged.');
      $this->drupalPost('cart/checkout', $edit, t('Review order'));
      $new_order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField();
      $this->assertEqual($order_id, $new_order_id, 'Original order_id was reused.');

      // Jump 10 minutes into the future.
      db_update('uc_orders')
        ->fields(array(
            'modified' => time() - UC_CART_ORDER_TIMEOUT - 1,
          ))
        ->condition('order_id', $order_id)
        ->execute();
      $old_order = uc_order_load($order_id);

      // Go to a different page, then back to order - verify that we are using a new order.
      $this->drupalGet('<front>');
      $this->drupalPost('cart', array(), 'Checkout');
      $this->assertNoRaw($oldname, 'Customer name was cleared after timeout.');
      $newname = $this->randomName(10);
      $edit['panes[delivery][delivery_first_name]'] = $newname;
      $this->drupalPost('cart/checkout', $edit, t('Review order'));
      $new_order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $newname))->fetchField();
      $this->assertNotEqual($order_id, $new_order_id, 'New order was created after timeout.');

      // Verify that the status of old order is abandoned.
      $old_order = uc_order_load($order_id, TRUE);
      $this->assertEqual($old_order->order_status, 'abandoned', 'Original order was marked abandoned.');
    }
    else {
      $this->fail('No order was created.');
    }
  }

}

/**
 * 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');
    $this->assertField(
      'uc_add_item_redirect',
      t('Add to cart redirect field exists')
    );

    $redirect = $this->randomName(8);
    $this->drupalPost(
      'admin/store/settings/cart',
      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');
    $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');
    $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');
    $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');
    $this->assertField(
      'uc_checkout_enabled',
      t('Enable checkout field exists')
    );

    $this->drupalPost(
      'admin/store/settings/checkout',
      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');
    $this->assertField(
      'uc_checkout_anonymous',
      t('Anonymous checkout field exists')
    );

    $this->drupalPost(
      'admin/store/settings/checkout',
      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.')
    );
  }
}
