<?php

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


/**
 * Abstract class for Commerce cart testing. All Commerce cart tests should
 * extend this class.
 */
abstract class CommerceCartTestCase extends CommerceBaseTestCase {

  /**
   * Helper function to  perform the common test tasks for cart testing.
   * @see CommerceBaseTestCase::setUpHelper()
   */
  protected function setUpHelper($set = 'all', array $other_modules = array()) {
    $modules = parent::setUpHelper($set, $other_modules);
    parent::setUp($modules);

    $this->store_admin = $this->createStoreAdmin();
    $this->store_customer = $this->createStoreCustomer();
  }
}


/**
 * Test cart features for a product display that only has one product attached.
 */
class CommerceCartTestCaseSimpleProduct extends CommerceCartTestCase {
  /**
   * Product that is being added to the cart.
   */
  protected $product;

  /**
   * Product display.
   */
  protected $product_node;


  /**
   * Implementation of getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Shopping cart',
      'description' => 'Test cart features like adding products to the cart, removing products from the cart and updating quantities.',
      'group' => 'Drupal Commerce',
    );
  }

  /**
   * Implementation of setUp().
   */
  function setUp() {
    parent::setUpHelper('all');
    // 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);

    // Log as a normal user to test cart process.
    $this->drupalLogin($this->store_customer);

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

	/**
   * Test if the product form has the correct structure.
   */
  public function testCommerceCartProductFormStructure() {
    // Go to cart url.
    $this->drupalGet('node/' . $this->product_node->nid);

    $this->assertField('edit-submit', t('Add to cart button exists'));
  }

  /**
   * Test if the product has been correctly added to the cart.
   */
  public function testCommerceCartAdd() {
    // Ensure the add to cart message is displayed.
    $message = t('%title added to <a href="!cart-url">your cart</a>.', array('%title' => $this->product_node->title, '!cart-url' => url('cart')));
    $this->assertRaw($message, t('Product add to cart message displayed.'));

    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));

    // Test if the page resolves and there is something in the cart.
    $this->assertResponse(200);
    $this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
    $this->assertText($this->product->title, t('Product was added to the cart'));
  }

	/**
   * Test if the cart form has the correct fields and structure.
   */
  public function testCommerceCartFormStructure() {
    // Check if the form is present and it has the quantity field, remove and
    //  submit buttons.
    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));

    // Check remove button.
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-delete')]", NULL, t('Remove button present'));

    // Check quantity field.
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", NULL, t('Quantity field present'));
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", 1, t('Quantity field has correct number of items'));

    // Check if the Update cart and Checkout buttons are present.
    $this->assertField("edit-submit", t('Update cart button present'));
    $this->assertField("edit-checkout", t('Checkout button present'));
  }

	/**
   * Test if the product is present in the order stored in db.
   */
  public function testCommerceCartOrder() {
    // Load the current order of the user.
    $order = commerce_cart_order_load($this->store_customer->uid);
    $products = array();
    $this->assertTrue(commerce_cart_order_is_cart($order), t('User has currently an order in cart status.'));
    // Get the products out of the order and store them in an array.
    foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) {
       if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) {
         $product = $line_item_wrapper->commerce_product->value();
         $products[$product->product_id]= $product;
       }
    }
    // Check if the product is in the products array for the order.
    $this->assertTrue(in_array($this->product->product_id, array_keys($products)), t('Product is actually in the cart'));
  }

	/**
   * Test the quantity changes in the cart.
   */
  public function testCommerceCartChangeQty() {
    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));
    // Change quantity in the cart view form.
    // We search for the first quantity field in the html and change the
    //   amount there.
    $qty = $this->xpath("//input[starts-with(@name, 'edit_quantity')]");
    $this->drupalPost($this->getCommerceUrl('cart'), array((string) $qty[0]['name'] => 2), t('Update cart'));
    // Check if the amount has been really changed.
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", 2, t('Cart updated with new quantity'));
  }

	/**
   * Test removing a product from the cart.
   */
  public function testCommerceCartRemove() {
    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));
    // Remove the product from the cart.
    $this->drupalPost(NULL, array(), t('Remove'));
    // Test if the page resolves and there is something in the cart.
    $this->assertText(t('Your shopping cart is empty.'), t('Removed product and cart is empty'));
  }
}

/**
 * Test cart features for a product display that has several products attached.
 */
class CommerceCartTestCaseMultiProducts extends CommerceCartTestCase {
  /**
   * Products that are being added to the cart.
   */
  protected $products = array();

  /**
   * Titles of the products that are being added to the cart.
   */
  protected $product_titles = array();

  /**
   * Product display.
   */
  protected $product_node;

  /**
   * Implementation of getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Shopping cart multiple',
      'description' => 'Test adding products to cart from a product display node with multiple products, using the product select add to cart form.',
      'group' => 'Drupal Commerce',
    );
  }

  /**
   * Implementation of setUp().
   */
  function setUp() {
    parent::setUpHelper('all');
    // Create a dummy product display content type.
    $this->createDummyProductDisplayContentType('product_display', TRUE, 'field_product', 2);

    // Create dummy product display nodes (and their corresponding product
    //  entities).
    $products = array();
    $sku = 'PROD-01';
    $product_name = 'Product One';
    $this->product_titles[] = $product_name;
    $product = $this->createDummyProduct($sku, $product_name);
    $this->products[$product->product_id] = $product;
    $sku = 'PROD-02';
    $product_name = 'Product Two';
    $this->product_titles[] = $product_name;
    $product = $this->createDummyProduct($sku, $product_name);
    $this->products[$product->product_id] = $product;
    $this->product_node = $this->createDummyProductNode(array_keys($this->products), 'Combined Product');

    // Log as a normal user to test cart process.
    $this->drupalLogin($this->store_customer);

    // Submit the add to cart form.
    $this->drupalPost('node/' . $this->product_node->nid, array('product_id' => $this->products[2]->product_id), t('Add to cart'));
  }

  /**
   * Test the structure of the product form.
   */
  public function testCommerceCartProductFormStructure() {
    $option_titles = array();
    // Get the options of the product's select.
    $options = $this->xpath("//select[@id='edit-product-id']//option");
    foreach ($options as $option) {
      $option_titles[] = (string) $option;
    }

    // Check if the selector exists.
    $this->assertField('edit-product-id', t('The selector of products exists'));

    // Check if the products actually are present in the selector.
    $this->assertTrue($this->product_titles == $option_titles, t('Correct products are present in the selector'));

    // Look for the Add to cart button.
    $this->assertField('edit-submit', t('Add to cart button exists'));
  }

  /**
   * Test to select one product and check if it has been correctly added to
   *  the cart.
   */
  public function testCommerceCartSelectProductAdd() {
    // Ensure the add to cart message is displayed.
    $message = t('%title added to <a href="!cart-url">your cart</a>.', array('%title' => $this->product_titles[1], '!cart-url' => url('cart')));
    $this->assertRaw($message, t('Product add to cart message displayed.'));

    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));

    // Test if the page resolves and there is something in the cart.
    $this->assertResponse(200);
    $this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
    $this->assertText($this->product_titles[1], t('Product was added to the cart'));
  }

	/**
   * Test if the cart form has the correct fields and structure.
   */
  public function testCommerceCartFormStructure() {
    // Check if the form is present and it has the quantity field, remove and
    //  submit buttons.
    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));

    // Check remove button.
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-delete')]", NULL, t('Remove button present'));

    // Check quantity field.
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", NULL, t('Quantity field present'));
    $this->assertFieldByXPath("//input[starts-with(@id, 'edit-edit-quantity')]", 1, t('Quantity field has correct number of items'));

    // Check if the Update cart and Checkout buttons are present.
    $this->assertField("edit-submit", t('Update cart button present'));
    $this->assertField("edit-checkout", t('Checkout button present'));
  }

	/**
   * Test if the product is present in the order stored in db.
   */
  public function testCommerceCartOrder() {
    $products_in_cart = array();
    // Load the current order of the user.
    $order = commerce_cart_order_load($this->store_customer->uid);

    // Check if the user has at least one order in cart status.
    $this->assertTrue(commerce_cart_order_is_cart($order), t('User has currently an order in cart status.'));

    // Get the products out of the order and store them in an array.
    foreach (entity_metadata_wrapper('commerce_order', $order)->commerce_line_items as $delta => $line_item_wrapper) {
       if (in_array($line_item_wrapper->type->value(), commerce_product_line_item_types())) {
         $product = $line_item_wrapper->commerce_product->value();
         $products_in_cart[$product->product_id]= $product;
       }
    }

    // Check if the product is in the products array for the order.
    $this->assertTrue(in_array($this->products[2]->product_id, array_keys($products_in_cart)), t('Product is actually in the cart'));
  }
}

/**
 * Test cart features for a product with attributes.
 */
class CommerceCartTestCaseAttributes extends CommerceCartTestCase {
  /**
   * Products that are being added to the cart.
   */
  protected $products = array();

  /**
   * Product display.
   */
  protected $product_node;

  /**
   * Implementation of getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Shopping cart attributes',
      'description' => 'Test adding products to cart from a product with multiple attributes, using the add to cart form product attribute select.',
      'group' => 'Drupal Commerce',
    );
  }

  /**
   * Implementation of setUp().
   */
  function setUp() {
    parent::setUpHelper('all');
    // Create a dummy product display content type.
    $this->createDummyProductDisplayContentType('product_display', TRUE, 'field_product', FIELD_CARDINALITY_UNLIMITED);

    // Create the fields and bind them to the product.
    $this->fields['field_1'] = array(
    	'field_name' => 'field_1',
    	'type' => 'list_text',
    	'cardinality' => 1,
    	'settings' => array(
        'allowed_values' => array('field_1_value_1' => 'field_1_value_1', 'field_1_value_2' => 'field_1_value_2'),
      ),
    );
    $this->fields['field_1'] = field_create_field($this->fields['field_1']);
    $this->fields['field_2'] = array(
    	'field_name' => 'field_2',
    	'type' => 'list_text',
    	'cardinality' => 1,
    	'settings' => array(
        'allowed_values' => array('field_2_value_1' => 'field_2_value_1', 'field_2_value_2' => 'field_2_value_2'),
      ),
    );
    $this->fields['field_2'] = field_create_field($this->fields['field_2']);
    foreach ($this->fields as $field) {
      $instance = array(
        'field_name' => $field['field_name'],
        'entity_type' => 'commerce_product',
        'bundle' => 'product',
        'label' => $field['field_name']. '_label',
        'description' => $field['field_name'] . '_description',
        'required' => TRUE,
        'widget' => array(
          'module' => 'options',
          'type' => 'options_select',
        ),
        'commerce_cart_settings' => array(
          'attribute_field' => TRUE,
          'attribute_widget' => 'select',
        ),
      );
      field_create_instance($instance);
    }

    // Populate the different values for the fields and create products.
    foreach ($this->fields['field_1']['settings']['allowed_values'] as  $field_1_value) {
      foreach ($this->fields['field_2']['settings']['allowed_values'] as $field_2_value) {
        $product = $this->createDummyProduct('PROD-' . $field_1_value . '-' . $field_2_value , $field_1_value.'_'.$field_2_value);
        $product->field_1[LANGUAGE_NONE][0]['value'] = $field_1_value;
        $product->field_2[LANGUAGE_NONE][0]['value'] = $field_2_value;
        $product->is_new = FALSE;
        commerce_product_save($product);
        $this->products[$product->product_id] = $product;
      }
    }

    // Create dummy product display node.
    $this->product_node = $this->createDummyProductNode(array_keys($this->products), 'Combined Product');

    // Log as a normal user to test cart process.
    $this->drupalLogin($this->store_customer);
  }

  /**
   * Test the add to cart functional process with attributes.
   */
  public function testCommerceCartSelectProductAdd() {
    // Go to product page.
    $this->drupalGet('node/' . $this->product_node->nid);

    // Set the product that we are checking.
    $product_wrapper = entity_metadata_wrapper('commerce_product', $this->products[3]);

    // Select one of the attributes.
    $this->drupalPostAJAX(NULL, array('attributes[field_1]' => $product_wrapper->field_1->value()), 'attributes[field_1]');

    // Add product to the cart.
    $this->drupalPost(NULL, array(), t('Add to cart'));

    // Ensure the add to cart message is displayed.
    $message = t('%title added to <a href="!cart-url">your cart</a>.', array('%title' => 'field_1_value_2_field_2_value_1', '!cart-url' => url('cart')));
    $this->assertRaw($message, t('Product add to cart message displayed.'));

    // Go to cart url.
    $this->drupalGet($this->getCommerceUrl('cart'));

    // Test if the page resolves and there is something in the cart.
    $this->assertResponse(200);
    $this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
    $this->assertText('field_1_value_2_field_2_value_1', t('Product was added to the cart'));

  }

  /**
   * Test the form structure of the Product.
   */
  public function testCommerceCartProductFormStructure() {
    $this->drupalGet('node/' . $this->product_node->nid);
    // Check whether the attribute selectors exist.
    $this->assertField('edit-attributes-field-1', t('First attribute selector exists'));
    $this->assertField('edit-attributes-field-2', t('Second attribute selector exists'));

    // Check the number of attributes.
    $options = $this->xpath("//select[@id='edit-attributes-field-1']//option");
    $this->assertEqual(count($options), count($this->fields['field_1']['settings']['allowed_values']), t('Number of options for first attribute match'));
    $options = $this->xpath("//select[@id='edit-attributes-field-2']//option");
    $this->assertEqual(count($options), count($this->fields['field_2']['settings']['allowed_values']), t('Number of options for second attribute match'));

    // Look for the Add to cart button.
    $this->assertField('edit-submit', t('Add to cart button exists'));
  }
}

/**
 * Test cart conversion from anonymous to authenticated.
 */
class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase {
  /**
   * Product that is being added to the cart.
   */
  protected $product;

  /**
   * Product display.
   */
  protected $product_node;


  /**
   * Implementation of getInfo().
   */
  public static function getInfo() {
    return array(
      'name' => 'Shopping cart anonymous to authenticated',
      'description' => 'Test cart conversion from anonymous to authenticated when an anonymous users logs in.',
      'group' => 'Drupal Commerce',
    );
  }

  /**
   * Implementation of setUp().
   */
  function setUp() {
    parent::setUpHelper('all');
    // Create a dummy product display content type.
    $this->createDummyProductDisplayContentType();

    // Create dummy product display nodes (and their corresponding product
    //  display).
    $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);
  }

  /**
   * Test anonymous cart conversion.
   */
  public function testCommerceCartAnonymousToAuthenticated() {
    // Logout to be anonymous and force user uid.
    $this->drupalLogout();
    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 just created.
    $order_anonymous = 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();

    // Access to the cart and check if the product is in it.
    $this->drupalGet($this->getCommerceUrl('cart'));
    $this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
    $this->assertText($this->product->title, t('Product was added to the cart'));

    // Change the price to check if the amount gets updated when the user logs
    // in.
    $new_price = $this->product->commerce_price[LANGUAGE_NONE][0]['amount'] + rand(2, 500);
    $this->product->commerce_price[LANGUAGE_NONE][0]['amount'] = $new_price;
    $this->product->is_new = FALSE;
    commerce_product_save($this->product);

    // Log in with normal user.
    $this->drupalPost('user', array('name' => $this->store_customer->name, 'pass' => $this->store_customer->pass_raw), t('Log in'));

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

    // Access to the cart and check if the product is in it.
    $this->drupalGet($this->getCommerceUrl('cart'));
    $this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty'));
    $this->assertText($this->product->title, t('Product is still in the cart'));
    $this->assertText(trim(commerce_currency_amount_to_decimal($this->product->commerce_price[LANGUAGE_NONE][0]['amount'], $this->product->commerce_price[LANGUAGE_NONE][0]['currency_code'])), t('Product price has been updated'));

    // Check if the order is the same.
    $this->assertTrue($order_anonymous->order_id == $order_authenticated->order_id, t('Cart has been converted successfully'));
  }

}
