<?php

/**
 * @file
 * Ubercart Attribute Tests
 */

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

/**
 * SimpleTests for the Ubercart Attributes API.
 */
class UbercartAttributeTestCase extends UbercartTestHelper {

  public static function getInfo() {
    return array(
      'name' => 'Attribute API',
      'description' => 'Test the attribute API.',
      'group' => 'Ubercart',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  function setUp() {
    parent::setUp(array('uc_attribute'), array('administer attributes', 'administer nodes'));
    $this->drupalLogin($this->adminUser);
  }

  /**
   * Tests the basic attribute API.
   */
  public function testAttributeAPI() {

    // Create an attribute.
    $attribute = self::createAttribute();

    // Test retrieval.
    $loaded_attribute = uc_attribute_load($attribute->aid);

    // Check the attribute integrity.
    foreach (self::attributeFieldsToTest() as $field) {
      if ($loaded_attribute->$field != $attribute->$field) {
        $this->fail(t('Attribute integrity check failed.'), t('Ubercart'));
        break;
      }
    }

    // Add a product.
    $product = $this->createProduct();

    // Attach the attribute to a product.
    uc_attribute_subject_save($attribute, 'product', $product->nid);

    // Confirm the database is correct.
    $this->assertEqual(
      $attribute->aid,
      db_result(db_query("SELECT aid FROM {uc_product_attributes} WHERE nid = %d", $product->nid)),
      t('Attribute was attached to a product properly.'),
      t('Ubercart')
    );
    $this->assertTrue(uc_attribute_subject_exists($attribute->aid, 'product', $product->nid));

    // Test retrieval.
    $loaded_attribute = uc_attribute_load($attribute->aid, $product->nid, 'product');

    // Check the attribute integrity.
    foreach (self::attributeFieldsToTest('product') as $field) {
      if ($loaded_attribute->$field != $attribute->$field) {
        $this->fail(t('Attribute integrity check failed.'), t('Ubercart'));
        break;
      }
    }

    // Delete it.
    uc_attribute_subject_delete($attribute->aid, 'product', $product->nid);

    // Confirm again.
    $this->assertFalse(
      db_result(db_query("SELECT aid FROM {uc_product_attributes} WHERE nid = %d", $product->nid)),
      t('Attribute was detached from a product properly.'),
      t('Ubercart')
    );
    $this->assertFalse(uc_attribute_subject_exists($attribute->aid, 'product', $product->nid));

    // Add a product class.
    $product_class = $this->createProductClass();

    // Attach the attribute to a product class.
    uc_attribute_subject_save($attribute, 'class', $product_class->pcid);

    // Confirm the database is correct.
    $this->assertEqual(
      $attribute->aid,
      db_result(db_query("SELECT aid FROM {uc_class_attributes} WHERE pcid = '%s'", $product_class->pcid)),
      t('Attribute was attached to a product class properly.'),
      t('Ubercart')
    );
    $this->assertTrue(uc_attribute_subject_exists($attribute->aid, 'class', $product_class->pcid));

    // Test retrieval.
    $loaded_attribute = uc_attribute_load($attribute->aid, $product_class->pcid, 'class');

    // Check the attribute integrity.
    foreach (self::attributeFieldsToTest('class') as $field) {
      if ($loaded_attribute->$field != $attribute->$field) {
        $this->fail(t('Attribute integrity check failed.'), t('Ubercart'));
        break;
      }
    }

    // Delete it.
    uc_attribute_subject_delete($attribute->aid, 'class', $product_class->pcid);

    // Confirm again.
    $this->assertFalse(
      db_result(db_query("SELECT aid FROM {uc_class_attributes} WHERE pcid = '%s'", $product_class->pcid)),
      t('Attribute was detached from a product class properly.'),
      t('Ubercart')
    );
    $this->assertFalse(uc_attribute_subject_exists($attribute->aid, 'class', $product_class->pcid));

    // Create a few more.
    for ($i = 0; $i < 5; $i++) {
      $a = self::createAttribute();
      $attributes[$a->aid] = $a;
    }

    // Add some options, organizing them by aid and oid.
    $attribute_aids = array_keys($attributes);

    $all_options = array();
    foreach ($attribute_aids as $aid) {
      for ($i = 0; $i < 3; $i++) {
        $option = self::createAttributeOption(array('aid' => $aid));
        $all_options[$option->aid][$option->oid] = $option;
      }
    }
    for ($i = 0; $i < 3; $i++) {
      $option = self::createAttributeOption(array('aid' => $attribute->aid));
      $all_options[$option->aid][$option->oid] = $option;
    }

    // Get the options.
    $attribute = uc_attribute_load($attribute->aid);

    // Load every attribute we got.
    $attributes_with_options = uc_attribute_load_multiple();

    // Make sure all the new options are on attributes correctly.
    foreach ($all_options as $aid => $options) {
      foreach ($options as $oid => $option) {
        foreach (self::attributeOptionFieldsToTest() as $field) {
          if ($option->$field != $attributes_with_options[$aid]->options[$oid]->$field) {
            $this->fail(t('Option integrity check failed.'), t('Ubercart'));
            break;
          }
        }
      }
    }

    // Pick 5 keys to check at random.
    $aids = drupal_map_assoc(array_rand($attributes, 3));

    // Load the attributes back.
    $loaded_attributes = uc_attribute_load_multiple($aids);

    // Make sure we only got the attributes we asked for. No more, no less.
    $this->assertEqual(count($aids), count($loaded_attributes), t('Verifying attribute result.'), t('Ubercart'));
    $this->assertEqual(count($aids), count(array_intersect_key($aids, $loaded_attributes)), t('Verifying attribute result.'), t('Ubercart'));

    // Check the attributes' integrity.
    foreach ($loaded_attributes as $aid => $loaded_attribute) {
      foreach (self::attributeFieldsToTest() as $field) {
        if ($attributes[$aid]->$field != $loaded_attributes[$aid]->$field) {
          $this->fail(t('Attribute integrity check failed.'), t('Ubercart'));
          break;
        }
      }
    }

    // Add the selected attributes to the product.
    foreach ($loaded_attributes as $loaded_attribute) {
      uc_attribute_subject_save($loaded_attribute, 'product', $product->nid, TRUE);
    }

    // Test loading all product attributes. (This covers uc_attribute_load_product_attributes(),
    // as the semantics are the same -cha0s)
    $loaded_product_attributes = uc_attribute_load_multiple(array(), 'product', $product->nid);

    // We'll get all in $loaded_attributes above, plus the original.
    $product_attributes = $loaded_attributes;

    // Make sure we only got the attributes we asked for. No more, no less.
    $this->assertEqual(count($loaded_product_attributes), count($product_attributes), t('Verifying attribute result.'), t('Ubercart'));
    $this->assertEqual(count($loaded_product_attributes), count(array_intersect_key($loaded_product_attributes, $product_attributes)), t('Verifying attribute result.'), t('Ubercart'));

    // Check the attributes' integrity.
    foreach ($loaded_product_attributes as $aid => $loaded_product_attribute) {
      foreach (self::attributeFieldsToTest('product') as $field) {
        if ($loaded_product_attributes[$aid]->$field != $product_attributes[$aid]->$field) {
          $this->fail(t('Attribute integrity check failed.'), t('Ubercart'));
          break;
        }
      }
    }

    // Make sure all the options are on attributes correctly.
    foreach ($all_options as $aid => $options) {
      foreach ($options as $oid => $option) {
        if (empty($loaded_product_attributes[$aid]) || empty($loaded_product_attributes[$aid]->options[$oid])) continue;

        foreach (self::attributeOptionFieldsToTest() as $field) {
          if ($option->$field != $loaded_product_attributes[$aid]->options[$oid]->$field) {
            $this->fail(t('Option integrity check failed.'), t('Ubercart'));
            break;
          }
        }
      }
    }

    // Add the selected attributes to the product.
    foreach ($loaded_attributes as $loaded_attribute) {
      uc_attribute_subject_save($loaded_attribute, 'class', $product_class->pcid, TRUE);
    }

    // Test loading all product attributes. (This covers uc_attribute_load_product_attributes(),
    // as the semantics are the same -cha0s)
    $loaded_class_attributes = uc_attribute_load_multiple(array(), 'class', $product_class->pcid);

    // We'll get all in $loaded_attributes above, plus the original.
    $class_attributes = $loaded_attributes;

    // Make sure we only got the attributes we asked for. No more, no less.
    $this->assertEqual(count($loaded_class_attributes), count($class_attributes), t('Verifying attribute result.'), t('Ubercart'));
    $this->assertEqual(count($loaded_class_attributes), count(array_intersect_key($loaded_class_attributes, $class_attributes)), t('Verifying attribute result.'), t('Ubercart'));

    // Check the attributes' integrity.
    foreach ($loaded_class_attributes as $aid => $loaded_class_attribute) {
      foreach (self::attributeFieldsToTest('class') as $field) {
        if ($loaded_class_attributes[$aid]->$field != $class_attributes[$aid]->$field) {
          $this->fail(t('Attribute integrity check failed.'), t('Ubercart'));
          break;
        }
      }
    }

    // Make sure all the options are on attributes correctly.
    foreach ($all_options as $aid => $options) {
      foreach ($options as $oid => $option) {
        if (empty($loaded_class_attributes[$aid]) || empty($loaded_class_attributes[$aid]->options[$oid])) continue;

        foreach (self::attributeOptionFieldsToTest() as $field) {
          if ($option->$field != $loaded_class_attributes[$aid]->options[$oid]->$field) {
            $this->fail(t('Option integrity check failed.'), t('Ubercart'));
            break;
          }
        }
      }
    }

    // Test deletion of base attribute.
    $aid = $attribute->aid;
    $options = $attribute->options;
    uc_attribute_delete($attribute->aid);

    $this->assertFalse(uc_attribute_load($attribute->aid), t('Attribute was deleted properly.'), t('Ubercart'));

    // Sanity check!
    $this->assertFalse(db_result(db_query("SELECT aid FROM {uc_attributes} WHERE aid = %d", $attribute->aid)), t('Attribute was seriously deleted properly!'), t('Ubercart'));

    // Test that options were deleted properly.
    foreach ($options as $option) {
      $this->assertFalse(db_result(db_query("SELECT oid FROM {uc_attribute_options} WHERE oid = %d", $option->oid)), t('Make sure options are deleted properly.'), t('Ubercart'));
    }

    // Test the deletion applied to products too.
    $loaded_product_attributes = uc_attribute_load_multiple(array(), 'product', $product->nid);

    // We'll get all in $loaded_attributes above, without the original. (Which
    // has been deleted.)
    $product_attributes = $loaded_attributes;

    // Make sure we only got the attributes we asked for. No more, no less.
    $this->assertEqual(count($loaded_product_attributes), count($product_attributes), t('Verifying attribute result.'), t('Ubercart'));
    $this->assertEqual(count($loaded_product_attributes), count(array_intersect_key($loaded_product_attributes, $product_attributes)), t('Verifying attribute result.'), t('Ubercart'));

    // Test the deletion applied to classes too.
    $loaded_class_attributes = uc_attribute_load_multiple(array(), 'class', $product_class->pcid);

    // We'll get all in $loaded_attributes above, without the original. (Which
    // has been deleted.)
    $class_attributes = $loaded_attributes;

    // Make sure we only got the attributes we asked for. No more, no less.
    $this->assertEqual(count($loaded_class_attributes), count($class_attributes), t('Verifying attribute result.'), t('Ubercart'));
    $this->assertEqual(count($loaded_class_attributes), count(array_intersect_key($loaded_class_attributes, $class_attributes)), t('Verifying attribute result.'), t('Ubercart'));

    // Add some adjustments.
    self::createProductAdjustment(array('combination' => 'a:1:{i:1;s:1:"1";}', 'nid' => 1));
    self::createProductAdjustment(array('combination' => 'a:1:{i:1;s:1:"2";}', 'nid' => 1));
    self::createProductAdjustment(array('combination' => 'a:1:{i:1;s:1:"3";}', 'nid' => 1));
    self::createProductAdjustment(array('combination' => 'a:1:{i:2;s:1:"1";}', 'nid' => 2));
    self::createProductAdjustment(array('combination' => 'a:1:{i:3;s:1:"1";}', 'nid' => 2));
    self::createProductAdjustment(array('combination' => 'a:1:{i:1;s:1:"2";}', 'nid' => 3));
    self::createProductAdjustment(array('combination' => 'a:1:{i:1;s:1:"3";}', 'nid' => 3));
    self::createProductAdjustment(array('combination' => 'a:1:{i:3;s:1:"2";}', 'nid' => 3));
    self::createProductAdjustment(array('combination' => 'a:1:{i:3;s:1:"3";}', 'nid' => 4));

    // Test deletion by nid.
    uc_attribute_adjustments_delete(array('nid' => 1));
    $this->assertEqual(6, db_result(db_query("SELECT COUNT(*) FROM {uc_product_adjustments}")), t('Ubercart'));

    // Test deletion by aid.
    uc_attribute_adjustments_delete(array('aid' => 2));
    $this->assertEqual(5, db_result(db_query("SELECT COUNT(*) FROM {uc_product_adjustments}")), t('Ubercart'));

    // Test deletion by oid.
    uc_attribute_adjustments_delete(array('oid' => 2));
    $this->assertEqual(3, db_result(db_query("SELECT COUNT(*) FROM {uc_product_adjustments}")), t('Ubercart'));

    // Test deletion by aid and oid.
    uc_attribute_adjustments_delete(array('aid' => 1, 'oid' => 3));
    $this->assertEqual(2, db_result(db_query("SELECT COUNT(*) FROM {uc_product_adjustments}")), t('Ubercart'));
  }

  /**
   * Tests the "add attribute" user interface.
   */
  public function testAttributeUIAddAttribute() {
    $this->drupalGet('admin/store/attributes/add');

    $this->AssertText(t('The name of the attribute used in administrative forms'), t('Attribute add form working.'), t('Ubercart'));

    $edit = (array) self::createAttribute(array(), FALSE);

    $this->drupalPost('admin/store/attributes/add', $edit, t('Submit'));

    $this->assertRaw('<td class="active">'. $edit['name'] .'</td>', t('Verify name field.'), t('Ubercart'));
    $this->assertRaw('<td>'. $edit['label'] .'</td>', t('Verify label field.'), t('Ubercart'));
    $this->assertRaw('<td>'. ($edit['required'] ? t('Yes') : t('No')) .'</td>', t('Verify required field.'), t('Ubercart'));
    $this->assertRaw('<td align="center">'. $edit['ordering'] .'</td>', t('Verify ordering field.'), t('Ubercart'));
    $types = _uc_attribute_display_types();
    $this->assertRaw('<td>'. $types[$edit['display']] .'</td>', t('Verify display field.'), t('Ubercart'));

    $aid = db_result(db_query("SELECT aid FROM {uc_attributes} WHERE name = '%s'", $edit['name']));
    $this->assertTrue($aid, t('Attribute was created.'));

    $attribute = uc_attribute_load($aid);
    $fields_ok = TRUE;
    foreach ($edit as $field => $value) {
      if ($attribute->$field != $value) {
        $this->showVar($attribute);
        $this->showVar($edit);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->AssertTrue($fields_ok, t('Attribute created properly.'), t('Ubercart'));
  }

  /**
   * Tests the attribute settings page.
   */
  public function testAttributeUISettings() {
    $product = $this->createProduct();
    $attribute = self::createAttribute(array(
      'display' => 1,
    ));

    $option = self::createAttributeOption(array(
      'aid' => $attribute->aid,
      'price' => 30,
    ));

    $attribute->options[$option->oid] = $option;
    uc_attribute_subject_save($attribute, 'product', $product->nid, TRUE);

    $context = array(
      'revision' => 'formatted',
      'location' => 'product-attribute-form-element',
      'subject' => array('attribute' => $attribute),
      'extras' => array('option' => $option),
    );

    $qty = $product->default_qty;
    if (!$qty) {
      $qty = 1;
    }

    $price_info = array(
      'price' => $option->price,
      'qty' => $qty,
    );
    $adjust_price = uc_price($price_info, $context);

    $price_info['price'] += $product->sell_price;
    $total_price = uc_price($price_info, $context);

    $raw = array(
      'none' => $option->name .'</option>',
      'adjustment' => $option->name .', +'. $adjust_price .'</option>',
      'total' => $total_price .'</option>',
    );

    foreach (array('none', 'adjustment', 'total') as $type) {
      $edit['uc_attribute_option_price_format'] = $type;
      $this->drupalPost('admin/store/settings/attributes', $edit, t('Save configuration'));

      $this->drupalGet('node/'. $product->nid);
      $this->AssertRaw($raw[$type], t('Attribute option pricing is correct.'), t('Ubercart'));
    }
  }

  /**
   * Tests the "edit attribute" user interface.
   */
  public function testAttributeUIEditAttribute() {
    $attribute = self::createAttribute();

    $this->drupalGet('admin/store/attributes/'. $attribute->aid .'/edit');

    $this->AssertText(t('Edit attribute: @name', array('@name' => $attribute->name)), t('Attribute edit form working.'), t('Ubercart'));

    $edit = (array) self::createAttribute(array(), FALSE);
    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/edit', $edit, t('Submit'));

    $attribute = uc_attribute_load($attribute->aid);

    $fields_ok = TRUE;
    foreach ($edit as $field => $value) {
      if ($attribute->$field != $value) {
        $this->showVar($attribute);
        $this->showVar($edit);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->AssertTrue($fields_ok, t('Attribute edited properly.'), t('Ubercart'));
  }

  /**
   * Tests the "delete attribute" user interface.
   */
  public function testAttributeUIDeleteAttribute() {
    $attribute = self::createAttribute();

    $this->drupalGet('admin/store/attributes/'. $attribute->aid .'/delete');

    $this->AssertText(t('Are you sure you want to delete the attribute @name?', array('@name' => $attribute->name)), t('Attribute delete form working.'), t('Ubercart'));

    $edit = (array) self::createAttribute();
    unset($edit['aid']);

    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/delete', array(), t('Delete'));

    $this->AssertText(t('Product attribute deleted.'), t('Attribute deleted properly.'), t('Ubercart'));
  }

  /**
   * Tests the attribute options user interface.
   */
  public function testAttributeUIAttributeOptions() {
    $attribute = self::createAttribute();
    $option = self::createAttributeOption(array('aid' => $attribute->aid));

    uc_attribute_option_save($option);

    $this->drupalGet('admin/store/attributes/'. $attribute->aid .'/options');

    $this->AssertText(t('Options for @name', array('@name' => $attribute->name)), t('Attribute options form working.'), t('Ubercart'));
  }

  /**
   * Tests the "add attribute option" user interface.
   */
  public function testAttributeUIAttributeOptionsAdd() {
    $attribute = self::createAttribute();

    $this->drupalGet('admin/store/attributes/'. $attribute->aid .'/options/add');

    $this->AssertText(t('Options for @name', array('@name' => $attribute->name)), t('Attribute options add form working.'), t('Ubercart'));

    $edit = (array) self::createAttributeOption(array('aid' => $attribute->aid), FALSE);
    unset($edit['aid']);

    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/options/add', $edit, t('Submit'));

    $option = db_fetch_object(db_query("SELECT * FROM {uc_attribute_options} WHERE aid = %d", $attribute->aid));

    $fields_ok = TRUE;
    foreach ($edit as $field => $value) {
      if ($option->$field != $value) {
        $this->showVar($option);
        $this->showVar($edit);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->assertTrue($fields_ok, t('Attribute option added successfully by form.'), t('Ubercart'));
  }

  /**
   * Tests the "edit attribute options" user interface.
   */
  public function testAttributeUIAttributeOptionsEdit() {
    $attribute = self::createAttribute();
    $option = self::createAttributeOption(array('aid' => $attribute->aid));

    uc_attribute_option_save($option);

    $this->drupalGet('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/edit');

    $this->AssertText(t('Edit option: @name', array('@name' => $option->name)), t('Attribute options edit form working.'), t('Ubercart'));

    $edit = (array) self::createAttributeOption(array('aid' => $attribute->aid), FALSE);
    unset($edit['aid']);
    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/edit', $edit, t('Submit'));

    $option = uc_attribute_option_load($option->oid);

    $fields_ok = TRUE;
    foreach ($edit as $field => $value) {
      if ($option->$field != $value) {
        $this->showVar($option);
        $this->showVar($edit);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->assertTrue($fields_ok, t('Attribute option edited successfully by form.'), t('Ubercart'));
  }

  /**
   * Tests the "bulk edit attribute options" user interface.
   */
  public function testAttributeUIAttributeOptionsBulkEdit() {
    $attribute = self::createAttribute();
    $option = self::createAttributeOption(array('aid' => $attribute->aid));
    uc_attribute_option_save($option);

    $class = $this->createProductClass();
    uc_attribute_subject_save($attribute, 'class', $class->pcid);
    uc_attribute_subject_option_save($option, 'class', $class->pcid);

    $product = $this->createProduct();
    uc_attribute_subject_save($attribute, 'product', $product->nid);
    uc_attribute_subject_option_save($option, 'product', $product->nid);

    $edit = (array) self::createAttributeOption(array('aid' => $attribute->aid), FALSE);
    unset($edit['aid']);
    $edit['bulk_update'] = TRUE;
    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/edit', $edit, t('Submit'));
    unset($edit['bulk_update']);

    $this->assertText('Bulk updates applied', t('Bulk update selected.'), t('Ubercart'));

    $option = uc_attribute_subject_option_load($option->oid, 'class', $class->pcid);
    $fields_ok = TRUE;
    foreach ($edit as $field => $value) {
      if ($option->$field != $value) {
        $this->showVar($option);
        $this->showVar($edit);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->assertTrue($fields_ok, t('Class bulk updated successfully.'), t('Ubercart'));

    $product_attributes = uc_attribute_load_multiple(array(), 'product', $product->nid);
    $fields_ok = TRUE;
    foreach ($edit as $field => $value) {
      if ($product_attributes[$attribute->aid]->options[$option->oid]->$field != $value) {
        $this->showVar($product_attributes);
        $this->showVar($edit);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->assertTrue($fields_ok, t('Product bulk updated successfully.'), t('Ubercart'));
  }

  /**
   * Tests the "delete attribute option" user interface.
   */
  public function testAttributeUIAttributeOptionsDelete() {
    $attribute = self::createAttribute();
    $option = self::createAttributeOption(array('aid' => $attribute->aid));

    uc_attribute_option_save($option);

    $this->drupalGet('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/delete');

    $this->AssertText(t('Are you sure you want to delete the option @name?', array('@name' => $option->name)), t('Attribute options delete form working.'), t('Ubercart'));

    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/delete', array(), t('Delete'));

    $option = uc_attribute_option_load($option->oid);

    $this->assertFalse($option, t('Attribute option deleted successfully by form'), t('Ubercart'));
  }

  /**
   * Tests the product class attribute user interface.
   */
  public function testAttributeUIClassAttributeOverview() {
    $class = $this->createProductClass();
    $attribute = self::createAttribute();

    $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/attributes');

    $this->assertText(t('You must first add attributes to this class.'), t('Class attribute form working.'), t('Ubercart'));

    uc_attribute_subject_save($attribute, 'class', $class->pcid);

    $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/attributes');

    $this->assertNoText(t('You must first add attributes to this class.'), t('Class attribute form working.'), t('Ubercart'));

    $a = (array) self::createAttribute(array(), FALSE);
    unset($a['name'], $a['description']);
    foreach ($a as $field => $value) {
      $edit["attributes[{$attribute->aid}][$field]"] = $value;
    }
    $this->showVar($edit);
    $this->drupalPost('admin/store/products/classes/'. $class->pcid .'/attributes', $edit, t('Save changes'));

    $attribute = uc_attribute_load($attribute->aid, $class->pcid, 'class');

    $fields_ok = TRUE;
    foreach ($a as $field => $value) {
      if ($attribute->$field != $value) {
        $this->showVar($attribute);
        $this->showVar($a);
        $fields_ok = FALSE;
        break;
      }
    }

    $this->assertTrue($fields_ok, t('Class attribute edited successfully by form.'), t('Ubercart'));

    $edit = array();
    $edit["attributes[{$attribute->aid}][remove]"] = TRUE;
    $this->drupalPost('admin/store/products/classes/'. $class->pcid .'/attributes', $edit, t('Save changes'));

    $this->assertText(t('You must first add attributes to this class.'), t('Class attribute form working.'), t('Ubercart'));
  }

  /**
   * Tests the "add product class attribute option" user interface.
   */
  public function testAttributeUIClassAttributeAdd() {
    $class = $this->createProductClass();
    $attribute = self::createAttribute();

    $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/attributes/add');

    $this->assertRaw(t('@attribute</option>', array('@attribute' => $attribute->name)), t('Class attribute add form working.'), t('Ubercart'));

    $edit["add_attributes[]"] = $attribute->aid;

    $this->drupalPost('admin/store/products/classes/'. $class->pcid .'/attributes/add', $edit, t('Add attributes'));

    $this->assertNoText(t('You must first add attributes to this class.'), t('Class attribute form working.'), t('Ubercart'));
  }

  /**
   * Tests the product class attribute option user interface.
   */
  public function testAttributeUIClassAttributeOptionOverview() {
    $class = $this->createProductClass();
    $attribute = self::createAttribute();
    $option = self::createAttributeOption(array('aid' => $attribute->aid));

    uc_attribute_subject_save($attribute, 'class', $class->pcid);

    $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/options');

    $this->assertRaw(t(' @option</label>', array('@option' => $option->name)), t('Class attribute option form working.'), t('Ubercart'));

    $o = (array) self::createAttributeOption(array('aid' => $attribute->aid), FALSE);
    unset($o['name'], $o['aid']);
    $o['select'] = TRUE;
    foreach ($o as $field => $value) {
      $edit["attributes[$attribute->aid][options][$option->oid][$field]"] = $value;
    }
    unset($o['select']);
    $edit["attributes[$attribute->aid][default]"] = $option->oid;
    $this->showVar($edit);
    $this->drupalPost('admin/store/products/classes/'. $class->pcid .'/options', $edit, t('Submit'));
    $this->assertText('The product class options have been saved.', t('Class attribute option saved.'), t('Ubercart'));
    $this->showVar($option);

    $option = uc_attribute_subject_option_load($option->oid, 'class', $class->pcid);

    $fields_ok = TRUE;
    foreach ($o as $field => $value) {
      if ($option->$field != $value) {
        $this->showVar($option);
        $this->showVar($o);
        $fields_ok = FALSE;
        break;
      }
    }
    $this->assertTrue($fields_ok, t('Class attribute option edited successfully by form.'), t('Ubercart'));
  }

  /**
   * Tests the product node attribute user interface.
   */
  public function testAttributeUIProductAttributeOverview() {
    $class = $this->createProductClass();
    $attribute = self::createAttribute();
    uc_attribute_subject_save($attribute, 'class', $class->pcid);
    $product = $this->createProduct(array('type' => $class->pcid));

    // Check product has attribute added by default.
    $loaded_attribute = uc_attribute_load($attribute->aid, $product->nid, 'product');
    $this->assertEqual($attribute->name, $loaded_attribute->name, t('Product attribute added by default.'), t('Ubercart'));
    uc_attribute_subject_delete($attribute->aid, 'product', $product->nid);

    $this->drupalGet('node/'. $product->nid .'/edit/attributes/add');
    $edit = array();
    $edit['add_attributes[]'] = $attribute->aid;
    $this->drupalPost(NULL, $edit, t('Add attributes'));
    $this->assertText($attribute->name, t('Product attribute added.'), t('Ubercart'));

    $edit = array();
    $edit["attributes[{$attribute->aid}][remove]"] = TRUE;
    $this->drupalPost(NULL, $edit, t('Save changes'));
    $this->assertText(t('You must first add attributes to this product.'), t('Product attribute removed.'), t('Ubercart'));

    $this->drupalPost(NULL, array(), t('Reset to defaults'));
    $this->drupalPost(NULL, array(), t('Reset'));
    $this->assertText($attribute->name, t('Product attribute reset successfully.'), t('Ubercart'));
  }

  /**
   * Tests the bulk product attribute update user interface.
   */
  public function testAttributeUIProductAttributeBulkUpdate() {
    $class = $this->createProductClass();
    $attribute = self::createAttribute();
    uc_attribute_subject_save($attribute, 'class', $class->pcid);
    $product = $this->createProduct(array('type' => $class->pcid));

    $edit = array();
    $edit["attributes[{$attribute->aid}][remove]"] = TRUE;
    $this->drupalPost('node/'. $product->nid .'/edit/attributes', $edit, t('Save changes'));
    $this->assertText(t('You must first add attributes to this product.'), t('Product attribute removed.'), t('Ubercart'));

    $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/attributes_bulk');
    $this->assertText($product->title, t('Product name found.'), t('Ubercart'));
    $this->drupalPost(NULL, array(), t('Flush all'));

    $this->drupalGet('node/'. $product->nid .'/edit/attributes');
    $this->assertText($attribute->name, t('Product attribute reset successfully.'), t('Ubercart'));
  }

  /**
   * Creates a product adjustment SKU.
   *
   * @param $data
   */
  public static function createProductAdjustment($data) {
    $adjustment = $data + array(
      'model' => self::randomName(8),
    );
    db_query("INSERT INTO {uc_product_adjustments} (nid, combination, model) VALUES (%d, '%s', '%s')", $adjustment['nid'], $adjustment['combination'], $adjustment['model']);
  }

  /**
   * Returns an array of available fields for product or class attributes.
   *
   * @param $type
   */
  protected static function attributeFieldsToTest($type = '') {
    $fields = array(
      'aid', 'name', 'ordering', 'required', 'display', 'description', 'label',
    );

    switch ($type) {
      case 'product':
      case 'class':
        $info = uc_attribute_type_info($type);
        $fields = array_merge($fields, array($info['id']));
        break;
    }
    return $fields;
  }

  /**
   * Tests the product class attribute option user interface.
   */
  protected static function attributeOptionFieldsToTest($type = '') {
    $fields = array(
      'aid', 'oid', 'name', 'cost', 'price', 'weight', 'ordering',
    );

    switch ($type) {
      case 'product':
      case 'class':
        $info = uc_attribute_type_info($type);
        $fields = array_merge($fields, array($info['id']));
        break;
    }
    return $fields;
  }

  /**
   * Tests the product class attribute option user interface.
   */
  public static function createAttribute($data = array(), $save = TRUE) {
    $attribute = $data + array(
      'name' => DrupalWebTestCase::randomName(8),
      'label' => DrupalWebTestCase::randomName(8),
      'description' => DrupalWebTestCase::randomName(8),
      'required' => mt_rand(0, 1) ? TRUE : FALSE,
      'display' => mt_rand(0, 3),
      'ordering' => mt_rand(-10, 10),
    );
    $attribute = (object) $attribute;

    if ($save) {
      uc_attribute_save($attribute);
    }
    return $attribute;
  }

  /**
   * Creates an attribute option.
   *
   * @param $data
   * @param $save
   */
  public static function createAttributeOption($data = array(), $save = TRUE) {
    $option = $data + array(
      'name' => DrupalWebTestCase::randomName(8),
      'cost' => mt_rand(-500, 500),
      'price' => mt_rand(-500, 500),
      'weight' => mt_rand(-500, 500),
      'ordering' => mt_rand(-10, 10),
    );
    $option = (object) $option;

    if ($save) {
      uc_attribute_option_save($option);
    }
    return $option;
  }

  /**
   * Creates an attribute option.
   *
   * @param $data
   * @param $save
   */
  function showVar($var) {
    $this->pass('<pre>'. print_r($var, TRUE) .'</pre>');
  }
}
