<?php

/**
 * @file
 * Functional tests for the commerce checkout module.
 */

/**
 * Test checkout process.
 */
class CommerceCheckoutTestProcess extends CommerceBaseTestCase {

  /**
   * Order object.
   */
  protected $order;

  /**
   * Implementation of getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Checkout process',
      'description' => 'Test the entire checkout process. Including anonymous checkout and checkout panes functionality.',
      'group' => 'Drupal Commerce',
    );
  }

  /**
   * Implementation of setUp().
   */
  function setUp() {
    $modules = parent::setUpHelper('all');
    parent::setUp($modules);

    // User creation for different operations.
    $this->site_admin = $this->createSiteAdmin();
    $this->store_admin = $this->createStoreAdmin();
    $this->store_customer = $this->createStoreCustomer();

    // The rule that sends a mail after checkout completion should be disabled
    //  as it returns an error caused by how mail messages are stored.
    $rules_config = rules_config_load('commerce_checkout_order_email');
    $rules_config->active = FALSE;
    $rules_config->save();
  }

  /**
   * Helper function to prepare an anonymous enviroment, it sets the user,
   *  products and prepares a cart.
   */
  protected function prepareAnonymousEnviroment() {
    // Login as admin user to grant permissions.
    $this->drupalLogin($this->site_admin);
    user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array(
      'access checkout' => TRUE,
    ));

    // Create a dummy product display content type.
    $this->createDummyProductDisplayContentType();
    // Create dummy product display nodes (and their corresponding product
    //  entities).
    $sku = 'PROD-01';
    $product_name = 'Product One';
    $this->product = $this->createDummyProduct($sku, $product_name);
    $this->product_node = $this->createDummyProductNode(array($this->product->product_id), $product_name);

    // Logout to test the checkout process with anonymous user.
    $this->drupalLogout();

    // Override user variable to get the enviroment fully set.
    global $user;
    $user = user_load(0);

    // Submit the add to cart form.
    $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart'));

    // Get the order for the anonymous user.
    $this->order = reset(commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE));
    // Reset the cache as we don't want to keep the lock.
    entity_get_controller('commerce_order')->resetCache();
  }

  /**
   * Test changing the weight and page of a pane.
   */
  public function testCommerceCheckoutPanesForm() {
    // Log in as store customer.
    $this->drupalLogin($this->store_customer);
    // Access to the config page for checkout forms
    $this->drupalGet('admin/commerce/config/checkout/form');
    $this->assertResponse(403, t('A normal user is not able to access to the checkout configuration form page.'));

    // Log in as store admin.
    $this->drupalLogin($this->store_admin);
    // Access to the config page for checkout forms.
    $this->drupalGet('admin/commerce/config/checkout/form');
    $this->assertResponse(200, t('Store admin can access to the checkout configuration form page.'));

    // Modify weight of the panes
    $this->drupalPost('admin/commerce/config/checkout/form', array('panes[cart_contents][weight]'=> 1), t('Save configuration'));
    $this->assertOptionSelected('edit-panes-cart-contents-weight', 1, t('Pane weight changed'));

    // Change one pane to other page
    $this->drupalPost('admin/commerce/config/checkout/form', array('panes[checkout_review][page]'=> 'disabled'), t('Save configuration'));
    $this->assertOptionSelected('edit-panes-checkout-review-page', 'disabled', t('Pane page changed'));
  }

  /**
   * Test the checkout process using an authenticated user.
   */
  public function testCommerceCheckoutProcessAuthenticatedUser() {
    // Log in as normal user.
    $this->drupalLogin($this->store_customer);

    // Order creation, in cart status.
    $this->order = $this->createDummyOrder($this->store_customer->uid);

    // Access to checkout page.
    $this->drupalGet($this->getCommerceUrl('checkout'));

    // Check if the page resolves and if the default panes are present.
    $this->assertResponse(200, t('The owner of the order can access to the checkout page'));
    $this->assertTitle(t('Checkout') . ' | Drupal', t('Title of the checkout phase is correct'));
    $this->assertText(t('Shopping cart contents'), t('Shopping cart contents pane is present'));
    $this->assertText(t('Billing information'), t('Billing information pane is present'));

    // We are testing with authenticated user, so no account information
    // should appear.
    $this->assertNoText(t('Account information'), t('Account information pane is not present'));

    // Generate random information, as city, postal code, etc.
    $address_info = $this->generateAddressInformation();

    // Fill in the billing address information.
    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);

    // Check if the country has been selected correctly, this uses XPath as the
    //  ajax call replaces the element and the id may change.
    $this->assertFieldByXPath("//select[starts-with(@id, 'edit-customer-profile-billing-commerce-customer-address')]//option[@selected='selected']", 'US', t('Country selected'));

    // Fill in the required information for billing pane, with a random State.
    $info = array(
      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
      'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
      'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
      'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
      'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
    );
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    // Check for default panes and information in this checkout phase.
    $this->pass(t('Checking the default panes and the page information:'));
    $this->assertTitle(t('Review order') . ' | Drupal', t('Title of the checkout phase \'Review order\' is correct'));
    $this->assertText($address_info['name_line'], t('Billing information for \'name_line\' is correct'));
    $this->assertText($address_info['thoroughfare'], t('Billing information for \'thoroughfare\' is correct'));
    $this->assertText($address_info['locality'], t('Billing information for \'locality\' is correct'));
    $this->assertText(trim($address_info['postal_code']), t('Billing information for \'postal_code\' is correct'));
    $this->assertText('United States', t('Billing information country is correct'));
    $this->assertText('Example payment', t('Example payment method pane is present'));

    // Load the order to check the status.
    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
    // Reset the cache as we don't want to keep the lock.
    entity_get_controller('commerce_order')->resetCache();

    // At this point we should be in Checkout Review.
    $this->assertEqual(reset($order)->status, 'checkout_review', t('Order status is \'Checkout Review\' in the review phase.'));

    // Test the back & continue buttons.
    $this->drupalPost(NULL, array(), t('Go back'));
    $this->assertTitle(t('Checkout') . ' | Drupal', t('When clicking in the \'Back\' button, the title displayed corresponds with the current checkout phase: \'Checkout\''));
    $this->drupalPost(NULL, array(), t('Continue to next step'));
    $this->assertTitle(t('Review order') . ' | Drupal', t('When clicking in the \'Continue\' button, the title displayed corresponds with the current checkout phase: \'Review order\''));

    // Finish checkout process
    $this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue to next step'));

    // Reload the order directly from db to update status.
    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);

    // Order status should be pending when completing checkout process.
    $this->assertEqual(reset($order)->status, 'pending', t('Order status is \'Pending\' after completing checkout'));
    // Check if the completion message has been displayed.
    $this->assertTitle(t('Checkout complete') . ' | Drupal', t('Title of the page is \'Checkout complete\' when finishing the checkout process'));
    $this->assertText(t('Your order number is @order-number.', array('@order-number' => $this->order->order_number)), t('Completion message for the checkout is correctly displayed'));
  }

  /**
   * Test the checkout validation panes with anonymous user.
   */
  public function testCommerceCheckoutValidatePanesAnonymousUser() {
    // Prepare the cart for Anonymous.
    $this->prepareAnonymousEnviroment();
    // Access to checkout page.
    $this->drupalGet($this->getCommerceUrl('checkout'));

    // Test billing information and account information panes.
    $this->assertText(t('Billing information'), t('Billing information pane is present in the checkout page'));
    $this->assertText(t('Account information'), t('Account information pane is present in the checkout page'));

    // Test validation messages not filling any information.
    $this->drupalPost(NULL, array(), t('Continue to next step'));

    // Get all panes from the system to get their forms.
    $panes = commerce_checkout_panes();

    // Test the validation of Billing Information pane.
    $callback = commerce_checkout_pane_callback($panes['customer_profile_billing'], 'checkout_form');
    $pane_form = drupal_get_form($callback, $panes['customer_profile_billing'], $this->order);
    foreach (element_children($pane_form['commerce_customer_address'][LANGUAGE_NONE][0]) as $key) {
      $element = $pane_form['commerce_customer_address'][LANGUAGE_NONE][0][$key];
      if ($element['#required'] && empty($element['#default_value'])) {
        $this->assertText(t('!pane_message field is required', array('!pane_message' => $element['#title'])), t('Check required billing information pane messages'));
      }
    }

    // Test the validation of Account pane.
    $callback = commerce_checkout_pane_callback($panes['account'], 'checkout_form');
    $pane_form = drupal_get_form($callback, $panes['account'], $this->order);
    foreach (element_children($pane_form['login']) as $key) {
      if ($pane_form['login'][$key]['#required']) {
        $this->assertText(t('!pane_message field is required', array('!pane_message' => $pane_form['login'][$key]['#title'])), t('Check required account pane message.'));
      }
    }

    // Generate random information, as city name, postal code etc.
    $address_info = $this->generateAddressInformation();

    // Also generate a not-valid mail address.
    $user_mail = $this->randomName();

    // Fill in the billing address information
    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);

    // Fill in the required information for billing pane, with a random State.
    $info = array(
      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
      'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
      'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
      'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
      'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
    );

    // Also add the mail for the account pane.
    $info += array(
      'account[login][mail]' => $user_mail,
    );

    // Go to the next checkout step with the required information.
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    // Check if the wrong e-mail address fails validation.
    $this->assertRaw(t('The e-mail address %mail is not valid.', array('%mail' => $user_mail)), t('A warning message is displayed when the e-mail address for the anonymous user is not valid'));

    // Fix it and continue to next step.
    $user_mail = $this->randomName() . '@example.com';
    $info['account[login][mail]'] = $user_mail;
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    $this->assertNoRaw(t('The e-mail address %mail is not valid.', array('%mail' => $user_mail)), t('A warning message is not displayed when the e-mail address for the anonymous user is valid'));

    // Finish checkout process for good.
    $this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue to next step'));
  }

  /**
   * Test the checkout process with anonymous user.
   */
  public function testCommerceCheckoutProcessAnonymousUser() {
    // Prepare the cart for Anonymous.
    $this->prepareAnonymousEnviroment();
    // Access to checkout page.
    $this->drupalGet($this->getCommerceUrl('checkout'));

    // Check if the page resolves and if the default panes are present
    $this->assertResponse(200, t('Anonymous user can access to the checkout page for the order.'));
    $this->assertTitle(t('Checkout') . ' | Drupal', t('Title of the checkout phase is correct'));
    $this->assertText(t('Shopping cart contents'), t('Shopping cart contents pane is present'));
    $this->assertText(t('Billing information'), t('Billing information pane is present'));
    $this->assertText(t('Account information'), t('Account information pane is present'));

    // Generate random information, as user mail, city, etc.
    $user_mail = $this->randomName() . '@example.com';
    $address_info = $this->generateAddressInformation();

    // Fill in the billing address information
    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);

    // Check if the country has been selected correctly, this uses XPath as the
    //  ajax call replaces the element and the id may change
    $this->assertFieldByXPath("//select[starts-with(@id, 'edit-customer-profile-billing-commerce-customer-address')]//option[@selected='selected']", 'US', t('Country selected'));

    // Fill in the required information for billing pane, with a random State.
    $info = array(
      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
      'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
      'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
      'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
      'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
    );

    // Also add the mail for the account pane.
    $info+= array(
      'account[login][mail]' => $user_mail,
    );

    // Go to the next checkout step with the required information.
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    // Check for default panes and information in this checkout phase.
    $this->pass(t('Checking the default panes and the page information:'));
    $this->assertTitle(t('Review order') . ' | Drupal', t('Title of the checkout phase \'Review order\' is correct'));
    $this->assertText($address_info['name_line'], t('Billing information for \'name_line\' is correct'));
    $this->assertText($address_info['thoroughfare'], t('Billing information for \'thoroughfare\' is correct'));
    $this->assertText($address_info['locality'], t('Billing information for \'locality\' is correct'));
    $this->assertText(trim($address_info['postal_code']), t('Billing information for \'postal_code\' is correct'));
    $this->assertText('United States', t('Billing information country is correct'));
    $this->assertText('Example payment', t('Example payment method pane is present'));
    $this->assertText($user_mail, t('Account information is correct'));

    // Load the order to check the status.
    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
    // Reset the cache as we don't want to keep the lock.
    entity_get_controller('commerce_order')->resetCache();

    // At this point we should be in Checkout Review.
    $this->assertEqual(reset($order)->status, 'checkout_review', t('Order status is \'Checkout Review\' in the review phase.'));

    // Finish checkout process
    $this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue to next step'));

    // Reload the order directly from db to check status.
    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);

    // Order status should be pending when completing checkout process.
    $this->assertEqual(reset($order)->status, 'pending', t('Order status is \'Pending\' after completing checkout.'));

    // Check if the completion message has been displayed.
    $this->assertTitle(t('Checkout complete') . ' | Drupal', t('Title of the page is \'Checkout complete\' when finishing the checkout process'));
    // Check completion message.
    $this->assertText(t('Your order number is @order-number.', array('@order-number' => $this->order->order_number)), t('Completion message for the checkout is correctly displayed'));
  }

  /**
   * Test the checkout process with anonymous user using an e-mail address that
   * belongs to an existing user, the final result should be the order
   * assigned to the existing user.
   */
  public function testCommerceCheckoutProcessAnonymousExistingUser() {
    // Prepare the cart for Anonymous.
    $this->prepareAnonymousEnviroment();
    // Access to checkout page.
    $this->drupalGet($this->getCommerceUrl('checkout'));

    // Generate random information.
    $address_info = $this->generateAddressInformation();

    // Fill in the billing address information
    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);

    // Fill in the required information for billing pane, with a random State.
    $info = array(
      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
      'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
      'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
      'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
      'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
    );

    // Also add the mail for the account pane.
    $info += array(
      'account[login][mail]' => $this->store_customer->mail,
    );

    // Go to the next checkout step with the required information.
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    // Finish checkout process
    $this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue to next step'));

    // Reload the order directly from db to check its owner.
    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);

    // Assert that the owner of the order is the owner of the e-mail address used.
    $this->assertEqual($this->store_customer->uid, reset($order)->uid, t('The order has been correctly assigned to the user owner of the mail address'));
  }

  /**
   * Test the checkout process with anonymous user using an e-mail addres that
   * doesn't exists in the system, the final result is that the user gets the
   * account created and the order is assigned to that user.
   */
  public function testCommerceCheckoutProcessAnonymousNonExistingUser() {
    // Prepare the cart for Anonymous.
    $this->prepareAnonymousEnviroment();
    // Access to checkout page.
    $this->drupalGet($this->getCommerceUrl('checkout'));

    // Generate random information, as user mail, city, etc.
    $user_mail = $this->randomName() . '@example.com';
    $address_info = $this->generateAddressInformation();

    // Fill in the billing address information
    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);

    // Fill in the required information for billing pane, with a random State.
    $info = array(
      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
      'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
      'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
      'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
      'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
    );

    // Also add the mail for the account pane.
    $info+= array(
      'account[login][mail]' => $user_mail,
    );

    // Go to the next checkout step with the required information.
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    // Finish checkout process
    $this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue to next step'));

    // Reload the order directly from db to check its owner.
    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);

    // Check if the order completion triggered the user creation rule.
    $user = user_load(reset($order)->uid);
    $this->assertEqual($user->mail, $user_mail, t('The e-mail address of the owner of the order matches the one in the checkout input'));
    $this->assertTrue($this->store_customer->uid < $user->uid, t('User id of the new user is higher than the last user created then it is a new account'));
  }

  /**
   * Test order completion page access.
   */
  public function testCommerceCheckoutAccessOrder() {
    // Log in as normal user.
    $this->drupalLogin($this->store_customer);


    // Create dummy product.
    $product = $this->createDummyProduct('PROD-01', 'Product One');

    // Order creation, in complete status.
    $this->order = $this->createDummyOrder($this->store_customer->uid, array($product->product_id => 1), 'complete');


    // Access to the complete page, this one should be always accesible.
    $this->assertCheckoutPageAccessible($this->order, 'complete');
  }

  public function assertCheckoutPageAccessible($order, $page) {
    $path = $this->getCommerceUrl('checkout') . '/' . $order->order_id . ($page ? '/' . $page : '');
    $this->drupalGet($path);
    return $this->assertEqual($this->getUrl(), url($path, array('absolute' => TRUE)),  t('@page checkout page is accessible.', array('@page' => $page)));
  }

  public function assertCheckoutPageNotAccessible($order, $page) {
    $path = $this->getCommerceUrl('checkout') . '/' . $order->order_id . ($page ? '/' . $page : '');
    $this->drupalGet($path);
    return $this->assertNotEqual($this->getUrl(), url($path, array('absolute' => TRUE)),  t('@page checkout page is not accessible.', array('@page' => $page)));
  }

  /**
   * Test order completion page access.
   */
  public function testCommerceCheckoutAccessPages() {
    // Log in as normal user.
    $this->drupalLogin($this->store_customer);

    // Order creation, in cart status.
    $this->order = $this->createDummyOrder($this->store_customer->uid);

    // At this point, the rest of checkout pages shouldn't be accessible.
    $this->assertCheckoutPageAccessible($this->order, '');
    $this->assertCheckoutPageNotAccessible($this->order, 'review');
    $this->assertCheckoutPageNotAccessible($this->order, 'payment');
    $this->assertCheckoutPageNotAccessible($this->order, 'complete');

    // Generate random information, as city, postal code, etc.
    $address_info = $this->generateAddressInformation();

    // Fill in the billing address information
    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);

    // Check if the country has been selected correctly, this uses XPath as the
    //  ajax call replaces the element and the id may change
    $this->assertFieldByXPath("//select[starts-with(@id, 'edit-customer-profile-billing-commerce-customer-address')]//option[@selected='selected']", 'US', t('Country selected'));

    // Fill in the required information for billing pane, with a random State.
    $info = array(
      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
    	'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
    	'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
    	'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
    	'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
    );
    $this->drupalPost(NULL, $info, t('Continue to next step'));

    // At this point, only first page and review should be accessible, but the
    //  rest shouldn't.
    $this->assertCheckoutPageAccessible($this->order, '');
    $this->assertCheckoutPageAccessible($this->order, 'review');
    $this->assertCheckoutPageNotAccessible($this->order, 'payment');
    $this->assertCheckoutPageNotAccessible($this->order, 'complete');

    // Fill in the payment method and continue the process.
    $this->drupalPost(NULL, array('commerce_payment[payment_details][name]' => 'Example payment method'), t('Continue to next step'));

    // At this point, only the complete page should be accessible.
    $this->assertCheckoutPageNotAccessible($this->order, '');
    $this->assertCheckoutPageNotAccessible($this->order, 'review');
    $this->assertCheckoutPageNotAccessible($this->order, 'payment');
    $this->assertCheckoutPageAccessible($this->order, 'complete');
  }

}
