<?php

/**
 * @file
 * Unit tests for the commerce product module.
 */

/**
 * Test the product and product type CRUD.
 */
class CommerceProductCRUDTestCase extends CommerceBaseTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Product CRUD',
      'description' => 'Test the product CRUD functions.',
      'group' => 'Drupal Commerce',
    );
  }

  function setUp() {
    $modules = parent::setUpHelper('all');
    $modules[] = 'commerce_product_crud_test';
    parent::setUp($modules);

    $this->site_admin = $this->createSiteAdmin();
    cache_clear_all(); // Just in case
  }

  /**
   * Ensure the default product types are created.
   */
  function testCommerceProductDefaultProducts() {
    $default_types = array(
      'product' => 'Product',
    );

    // Load the default product types.
    $types_created = commerce_product_types();

    // Ensure each type exists.
    foreach ($default_types as $type => $name) {
      $this->assertTrue(!empty($types_created[$type]), 'Product type ' . check_plain($type) . ' has been created.');
    }
  }

  /**
   * Test the product type CRUD functions.
   */
  function testCommerceProductTypeCrud() {
    // Ensure commerce_product_ui_product_type_new() returns a valid empty product type.
    $new_product_type = commerce_product_ui_product_type_new();
    $this->assertNotNull($new_product_type['type'], 'commerce_product_ui_product_type_new() instantiated the type property.');
    $this->assertNotNull($new_product_type['name'], 'commerce_product_ui_product_type_new() instantiated the help property.');
    $this->assertNotNull($new_product_type['description'], 'commerce_product_ui_product_type_new() instantiated the help property.');
    $this->assertNotNull($new_product_type['help'], 'commerce_product_ui_product_type_new() instantiated the help property');

    // Supply customer values for the product type properties.
    $type = $this->randomName(20);
    $name = $this->randomName(40);
    $description = $this->randomString(128);
    $help = $this->randomString(128);

    // Add the values to the new content type.
    $new_product_type['type'] = $type;
    $new_product_type['name'] = $name;
    $new_product_type['description'] = $description;
    $new_product_type['help'] = $help;
    $new_product_type['is_new'] = TRUE;

    // Ensure content_product_ui_product_type_save() returns the proper value when inserting.
    $return = commerce_product_ui_product_type_save($new_product_type);
    $this->assertEqual($return, SAVED_NEW, 'commerce_product_ui_product_type_save() returned SAVED_NEW when saving a new product type.');

    // Load the newly saved content type.
    $saved_product_type = commerce_product_type_load($type);

    // Ensure the values that were saved match the values that we created.
    $this->assertTrue($saved_product_type, 'commerce_product_type_load() loaded the new product type.');
    $this->assertEqual($type, $saved_product_type['type'], 'The new product type type was properly saved and loaded.');
    $this->assertEqual($name, $saved_product_type['name'], 'The new product type name was properly saved and loaded.');
    $this->assertEqual($description, $saved_product_type['description'], 'The new product type description text was properly saved and loaded.');
    $this->assertEqual($help, $saved_product_type['help'], 'The new product type help text was properly saved and loaded.');

    // Alter the title, to ensure the update function works.
    $altered_name = $this->randomName(40);
    $saved_product_type['name'] = $altered_name;

    // Ensure commerce_product_ui_product_type_save() returns the proper value when updating.
    $return = commerce_product_ui_product_type_save($saved_product_type);
    $this->assertEqual($return, SAVED_UPDATED, 'commerce_product_ui_product_type_save() returned SAVED_UPDATED when saving an updated product type.');

    // Reset the cached product types, and verify commerce_product_types load the saved type.
    commerce_product_types_reset();
    $types = commerce_product_types();
    $this->assertNotNull($types[$type], 'commerce_product_types_reset() successfully reset the product types.');
    $this->assertEqual($saved_product_type['name'], $altered_name, 'commerce_product_ui_product_type_save() successfully updated the product type name.');

    // Ensure commerce_product_ui_product_type_delete() deletes a content type.
    commerce_product_ui_product_type_delete($type);
    $deleted_type = commerce_product_type_load($type);
    $this->assertFalse($deleted_type, 'commerce_product_ui_product_type_delete() successfully removed a product type.');
  }

  /**
   * Test the product CRUD functions.
   */
  function testCommerceProductCrud() {
    global $commerce_product_crud_tests;

    // Ensure commerce_product_new() returns a new product.
    $new_product = commerce_product_new('product');
    $fields = array('sku', 'type', 'title', 'uid');
    foreach ($fields as $field) {
      $this->assertNotNull($new_product->{$field}, 'commerce_product_new() instantiated the ' . check_plain($field) . ' property.');
    }

    $new_product->sku = $sku = $this->randomName(10);
    $new_product->type = $type = 'product';
    $new_product->title = $title = $this->randomName(10);
    $new_product->uid = 1;

    // Ensure commerce_product_save() returns SAVED_NEW when saving a new product
    $return = commerce_product_save($new_product);
    $this->assertIdentical($return, SAVED_NEW, 'commerce_product_save() successfully saved the new product.');

    // Ensure commerce_product_load() loaded the saved product.
    $loaded_product = commerce_product_load($new_product->product_id);
    foreach ($fields as $field) {
      $this->assertEqual($loaded_product->{$field}, $new_product->{$field}, 'The ' . check_plain($field) . ' value loaded by commerce_product_load() matches the value saved by commerce_product_save()');
    }

    $this->assertTrue($loaded_product->created > 0, 'commerce_product_save() added a created date to the product');
    $this->assertTrue($loaded_product->changed > 0, 'commerce_product_save() added a changed date to the product');

    // Ensure commerce_product_save() returns SAVED_UPDATED when saving an existing product.
    $loaded_product->uid = 0;
    $return = commerce_product_save($loaded_product);
    $this->assertIdentical($return, SAVED_UPDATED, 'commerce_product_save() successfully updated the product.');

    // Ensure hooks invoked during the save operation can load identical objects
    // be checking a global variable set in commerce_product_crud_test_commerce_product_update()
    // if the uid is in fact being loaded as 0 in the update hook.
    $this->assertTrue($commerce_product_crud_tests['hook_update_identical_uid'], t('Invoked hooks during save operation can successfully load identical object.'));

    // Ensure commerce_product_load_by_sku() can load a product by SKU.
    $loaded_product_by_sku = commerce_product_load_by_sku($sku);
    $this->assertEqual($loaded_product_by_sku->product_id, $new_product->product_id, 'The ID of the product loaded via commerce_product_load_by_sku() matches the saved product ID.');

    // Ensure commerce_product_load_multiple() can load multiple multiple products.
    $saved_product_ids = array();

    // First create and save multiple new products.
    for ($i = 0; $i < 3; $i++) {
      $product = commerce_product_new('product');
      $product->type = 'product';
      $product->sku = $this->randomName(10);
      $product->title = $this->randomName(10);
      $product->uid = 1;
      commerce_product_save($product);

      // Save the ID and title of the newly created product.
      $saved_products[$product->product_id] = $product->title;
    }

    $loaded_products = commerce_product_load_multiple(array_keys($saved_products));
    $this->assertEqual(count($saved_products), count($loaded_products), 'commerce_product_load_multiple() loaded the proper number of the products.');
    foreach ($loaded_products as $loaded_product) {
      $this->assertEqual($loaded_product->title, $saved_products[$loaded_product->product_id], 'commerce_product_load_multiple() successfully loaded a product.');
    }

    // Ensure commerce_product_delete() can remove a product.
    $return = commerce_product_delete($new_product->product_id);
    $this->assertTrue($return, 'commerce_product_delete() returned TRUE when deleting a product.');
    $deleted_product_load = commerce_product_load_multiple(array($new_product->product_id), array(), TRUE);
    $this->assertFalse($deleted_product_load, 'commerce_product_load_multiple() could not load the deleted product.');

    // Ensure commerce_product_delete_multiple() can delete multiple products.
    $return = commerce_product_delete_multiple(array_keys($saved_products));
    $this->assertTrue($return, 'commerce_product_delete_multiple() returned TRUE when deleting a product.');
    $deleted_products_load = commerce_product_load_multiple(array_keys($saved_products), array(), TRUE);
    $this->assertFalse($deleted_product_load, 'commerce_product_load_multiple() could not load the deleted products.');
  }

  /**
   * Test product Token replacement.
   */
  function testCommerceProductTokens() {
    $product = commerce_product_new('product');
    $creator = $this->drupalCreateUser();

    $product->sku = $this->randomName(10);
    $product->title = $this->randomName(10);
    $product->uid = $creator->uid;
    commerce_product_save($product);

    $this->assertEqual(token_replace('[commerce-product:product-id]', array('commerce-product' => $product)), $product->product_id, '[commerce-product:product-id] was replaced with the product ID.');
    $this->assertEqual(token_replace('[commerce-product:sku]', array('commerce-product' => $product)), $product->sku, '[commerce-product:sku] was replaced with the SKU.');
    $this->assertEqual(token_replace('[commerce-product:type]', array('commerce-product' => $product)), $product->type, '[commerce-product:type] was replaced with the product type.');
    $this->assertEqual(token_replace('[commerce-product:type-name]', array('commerce-product' => $product)), commerce_product_type_get_name($product->type), '[commerce-product:type-name] was replaced with the product type.');
    $this->assertEqual(token_replace('[commerce-product:title]', array('commerce-product' => $product)), $product->title, '[commerce-product:title] was replaced with the title.');
    $this->assertNotIdentical(strpos(token_replace('[commerce-product:edit-url]', array('commerce-product' => $product)), url('admin/commerce/products/' . $product->product_id . '/edit')), FALSE, '[commerce-product:edit-url] was replaced with the edit URL.');
    $this->assertEqual(token_replace('[commerce-product:creator:uid]', array('commerce-product' => $product)), $product->uid, '[commerce-product:creator:uid] was replaced with the uid of the creator.');
    $this->assertEqual(token_replace('[commerce-product:creator:name]', array('commerce-product' => $product)), check_plain(format_username($creator)), '[commerce-product:creator:name] was replaced with the name of the author.');
    $this->assertEqual(token_replace('[commerce-product:created]', array('commerce-product' => $product)), format_date($product->created, 'medium'), '[commerce-product:created] was replaced with the created date.');
    $this->assertEqual(token_replace('[commerce-product:changed]', array('commerce-product' => $product)), format_date($product->changed, 'medium'), '[commerce-product:changed] was replaced with the changed date.');
  }

  /**
   * Test product revision management.
   */
  function testCommerceProductRevisions() {
    $new_product = commerce_product_new('product');

    $new_product->sku = $this->randomName(10);
    $new_product->type = 'product';
    $new_product->title = $this->randomName(10);
    $new_product->uid = 1;
    commerce_product_save($new_product);

    // Ensure commerce_product_save() returns an entity with the revision_id set.
    $this->assertNotNull($new_product->revision_id, 'Revision id was created after first save.');

    // Ensure that on update with the revision set to TRUE a new revision is
    // created.
    $old_revision_id = $new_product->revision_id;
    $new_product->revision = TRUE;
    $new_product->title = $this->randomName(10);
    commerce_product_save($new_product);

    $this->assertNotEqual($old_revision_id, $new_product->revision_id, 'New revision was created');

    // Ensure that on update with the revision set to FALSE a new revision is
    // not created.
    $old_revision_id = $new_product->revision_id;
    $new_product->revision = FALSE;
    $new_product->title = $this->randomName(10);
    commerce_product_save($new_product);

    $this->assertEqual($old_revision_id, $new_product->revision_id, 'No new revision was created');
  }
}

/**
 * Test the Rules and Entity integration.
 *
 * TODO: replace this with a simpler event test that doesn't depend on the odd
 * product loading / saving interplay. I don't even think the unchanged
 * condition will ever work as is in here.
 *
class CommerceProductRulesTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Product rules integration',
      'description' => 'Tests the product rules integration.',
      'group' => 'Drupal Commerce',
    );
  }

  function setUp() {
    parent::setUp('commerce_product', 'rules');
  }

  /**
   * Calculates the output of t() given an array of placeholders to replace.
   *
  static function t($text, $strings) {
    $placeholders = array();
    foreach ($strings as $string) {
      $placeholders['%' . $string] = drupal_placeholder($string);
    }
    return strtr($text, $placeholders);
  }

  /**
   * Tests rules CRUD actions for products.
   *
  function testRulesCRUD() {
    // Test creation.
    $action = rules_action('entity_create', array(
      'type' => 'commerce_product',
      'param_type' => 'product',
      'param_sku' => 'foo',
      'param_title' => 'bar',
      'param_creator' => $GLOBALS['user'],
    ));
    // Test running access() and execute.
    $action->access();
    $action->execute();

    $text = RulesLog::logger()->render();
    $pos = strpos($text, self::t('Added the provided variable %entity_created of type %commerce_product', array('entity_created', 'commerce_product')));
    $pos = ($pos !== FALSE) ? strpos($text, self::t('Saved %entity_created of type %commerce_product.', array('entity_created', 'commerce_product')), $pos) : FALSE;
    $this->assertTrue($pos !== FALSE, 'Product has been created and saved.');

    $product = commerce_product_new('product');
    commerce_product_save($product);
    $rule = rule();
    $rule->action('entity_fetch', array('type' => 'commerce_product', 'id' => $product->product_id, 'entity_fetched:var' => 'product'));
    $rule->action('entity_save', array('data:select' => 'product', 'immediate' => TRUE));
    $rule->action('entity_delete', array('data:select' => 'product'));
    // Test running access and integrtiy check + execute.
    $rule->access();
    $rule->integrityCheck()->execute();
    $text = RulesLog::logger()->render();
    $pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array('entity_fetch')));
    $pos = ($pos !== FALSE) ? strpos($text, self::t('Added the provided variable %product of type %commerce_product', array('product', 'commerce_product')), $pos) : FALSE;
    $pos = ($pos !== FALSE) ? strpos($text, self::t('Saved %product of type %commerce_product.', array('product', 'commerce_product')), $pos) : FALSE;
    $pos = ($pos !== FALSE) ? strpos($text, self::t('Evaluating the action %entity_delete.', array('entity_delete')), $pos) : FALSE;
    $this->assertTrue($pos !== FALSE, 'Product has been fetched, saved and deleted.');
    $this->assertFalse(commerce_product_load($product->product_id), 'Product has been deleted.');
  }

  /**
   * Tests making use of product metadata.
   *
  function testProductPropertyInfo() {
    // Populate $values with all values that are setable. They will be set
    // with an metadata wrapper, so we also test setting that way.
    $values = array();
    $wrapper = entity_metadata_wrapper('commerce_product');
    foreach ($wrapper as $name => $child) {
      $info = $wrapper->$name->info();
      if (!empty($info['setter callback'])) {
        $info += array('type' => 'text');
        $values[$name] = $this->createValue($info['type'], $info);
      }
    }
    $values['type'] = 'product';
    $product = entity_create('commerce_product', $values);
    $this->assertTrue($product, "Created a product and set all setable values.");

    $wrapper = entity_metadata_wrapper('commerce_product', $product);
    foreach ($wrapper as $key => $child) {
      $this->assertValue($wrapper, $key);
    }
  }

  /**
   * Assert the value for the given property is returned.
   *
  protected function assertValue($wrapper, $key) {
    $this->assertTrue($wrapper->$key->value() !== NULL, check_plain($key) . ' property returned.');
    $info = $wrapper->$key->info();
    if (!empty($info['raw getter callback'])) {
      // Also test getting the raw value
      $this->assertTrue($wrapper->$key->raw() !== NULL, check_plain($key) . ' raw value returned.');
    }
  }

  /**
   * Creates a value for the given data type.
   *
  protected function createValue($type, $info) {
    if (!isset($this->node)) {
      // Create some entities to use the first time this runs.
      $this->node = $this->drupalCreateNode(array('type' => 'article'));
      $this->user = $this->drupalCreateUser();
    }

    if (isset($info['options list'])) {
      $options = array_keys($info['options list']());
      return entity_property_list_extract_type($type) ? array(reset($options)) : reset($options);
    }

    switch ($type) {
      case 'decimal':
      case 'integer':
      case 'duration':
        return 1;
      case 'date':
        return REQUEST_TIME;
      case 'boolean':
        return TRUE;
      case 'text':
        return drupal_strtolower($this->randomName(8));
      case 'text_formatted':
        return array('value' => $this->randomName(16));

      default:
        return $this->$type;
    }
  }


  /**
   * Tests making use of the product 'presave' event and loading the unchanged
   * product.
   *
  public function testProductEvent() {
    $rule = rules_reaction_rule();
    $rule->event('commerce_product_presave')
         ->condition('data_is', array('data:select' => 'product:type', 'value' => 'product'))
         ->condition(rules_condition('data_is', array('data:select' => 'product:sku', 'value:select' => 'product_unchanged:sku'))->negate())
         ->action('entity_delete', array('data:select' => 'product'));
    // Try running access and integrity checks.
    $rule->access();
    $rule->integrityCheck();
    // Save it.
    $rule->save('commerce_product_test1', 'commerce_product');

    // Force immediate cache clearing so we can test the rule *now*.
    rules_clear_cache(TRUE);

    // Create initial product.
    $product = commerce_product_new('product');
    $product->sku = 'foo';
    commerce_product_save($product);

    $this->assertTrue(commerce_product_load($product->product_id), 'Reaction rule not fired.');

    // Now update, so that the rule fires.
    $product->sku = 'bar';
    unset($product->is_new);
    commerce_product_save($product);
    $this->assertFalse(commerce_product_load($product->product_id), 'Reaction rule fired.');
    RulesLog::logger()->checkLog();
  }
}
 */
