diff --git modules/field/field.test modules/field/field.test new file mode 100644 index 0000000..36d6284 --- /dev/null +++ modules/field/field.test @@ -0,0 +1,2968 @@ +default_storage); + } + + /** + * Generate random values for a field_test field. + * + * @param $cardinality + * Number of values to generate. + * @return + * An array of random values, in the format expected for field values. + */ + function _generateTestFieldValues($cardinality) { + $values = array(); + for ($i = 0; $i < $cardinality; $i++) { + // field_test fields treat 0 as 'empty value'. + $values[$i]['value'] = mt_rand(1, 127); + } + return $values; + } + + /** + * Assert that a field has the expected values in an entity. + * + * This function only checks a single column in the field values. + * + * @param $entity + * The entity to test. + * @param $field_name + * The name of the field to test + * @param $langcode + * The language code for the values. + * @param $expected_values + * The array of expected values. + * @param $column + * (Optional) the name of the column to check. + */ + function assertFieldValues($entity, $field_name, $langcode, $expected_values, $column = 'value') { + $e = clone $entity; + field_attach_load('test_entity', array($e->ftid => $e)); + $values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array(); + $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.')); + foreach ($expected_values as $key => $value) { + $this->assertEqual($values[$key][$column], $value, t('Value @value was saved correctly.', array('@value' => $value))); + } + } +} + +class FieldAttachTestCase extends FieldTestCase { + function setUp($modules = array()) { + // Since this is a base class for many test cases, support the same + // flexibility that DrupalWebTestCase::setUp() has for the modules to be + // passed in as either an array or a variable number of string arguments. + if (!is_array($modules)) { + $modules = func_get_args(); + } + if (!in_array('field_test', $modules)) { + $modules[] = 'field_test'; + } + parent::setUp($modules); + + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + field_create_instance($this->instance); + } +} + +/** + * Unit test class for storage-related field_attach_* functions. + * + * All field_attach_* test work with all field_storage plugins and + * all hook_field_attach_pre_{load,insert,update}() hooks. + */ +class FieldAttachStorageTestCase extends FieldAttachTestCase { + public static function getInfo() { + return array( + 'name' => 'Field attach tests (storage-related)', + 'description' => 'Test storage-related Field Attach API functions.', + 'group' => 'Field API', + ); + } + + /** + * Check field values insert, update and load. + * + * Works independently of the underlying field storage backend. Inserts or + * updates random field data and then loads and verifies the data. + */ + function testFieldAttachSaveLoad() { + // Configure the instance so that we test hook_field_load() (see + // field_test_field_load() in field_test.module). + $this->instance['settings']['test_hook_field_load'] = TRUE; + field_update_instance($this->instance); + $langcode = LANGUAGE_NONE; + + $entity_type = 'test_entity'; + $values = array(); + + // TODO : test empty values filtering and "compression" (store consecutive deltas). + + // Preparation: create three revisions and store them in $revision array. + for ($revision_id = 0; $revision_id < 3; $revision_id++) { + $revision[$revision_id] = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); + // Note: we try to insert one extra value. + $values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); + $current_revision = $revision_id; + // If this is the first revision do an insert. + if (!$revision_id) { + $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id]; + field_attach_insert($entity_type, $revision[$revision_id]); + } + else { + // Otherwise do an update. + $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id]; + field_attach_update($entity_type, $revision[$revision_id]); + } + } + + // Confirm current revision loads the correct data. + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + // Number of values per field loaded equals the field cardinality. + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values')); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + // The field value loaded matches the one inserted or updated. + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current revision: expected value %delta was found.', array('%delta' => $delta))); + // The value added in hook_field_load() is found. + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Current revision: extra information for value %delta was found', array('%delta' => $delta))); + } + + // Confirm each revision loads the correct data. + foreach (array_keys($revision) as $revision_id) { + $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $entity)); + // Number of values per field loaded equals the field cardinality. + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + // The field value loaded matches the one inserted or updated. + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); + // The value added in hook_field_load() is found. + $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); + } + } + } + + /** + * Test the 'multiple' load feature. + */ + function testFieldAttachLoadMultiple() { + $entity_type = 'test_entity'; + $langcode = LANGUAGE_NONE; + + // Define 2 bundles. + $bundles = array( + 1 => 'test_bundle_1', + 2 => 'test_bundle_2', + ); + field_test_create_bundle($bundles[1]); + field_test_create_bundle($bundles[2]); + // Define 3 fields: + // - field_1 is in bundle_1 and bundle_2, + // - field_2 is in bundle_1, + // - field_3 is in bundle_2. + $field_bundles_map = array( + 1 => array(1, 2), + 2 => array(1), + 3 => array(2), + ); + for ($i = 1; $i <= 3; $i++) { + $field_names[$i] = 'field_' . $i; + $field = array('field_name' => $field_names[$i], 'type' => 'test_field'); + $field = field_create_field($field); + $field_ids[$i] = $field['id']; + foreach ($field_bundles_map[$i] as $bundle) { + $instance = array( + 'field_name' => $field_names[$i], + 'entity_type' => 'test_entity', + 'bundle' => $bundles[$bundle], + 'settings' => array( + // Configure the instance so that we test hook_field_load() + // (see field_test_field_load() in field_test.module). + 'test_hook_field_load' => TRUE, + ), + ); + field_create_instance($instance); + } + } + + // Create one test entity per bundle, with random values. + foreach ($bundles as $index => $bundle) { + $entities[$index] = field_test_create_stub_entity($index, $index, $bundle); + $entity = clone($entities[$index]); + $instances = field_info_instances('test_entity', $bundle); + foreach ($instances as $field_name => $instance) { + $values[$index][$field_name] = mt_rand(1, 127); + $entity->$field_name = array($langcode => array(array('value' => $values[$index][$field_name]))); + } + field_attach_insert($entity_type, $entity); + } + + // Check that a single load correctly loads field values for both entities. + field_attach_load($entity_type, $entities); + foreach ($entities as $index => $entity) { + $instances = field_info_instances($entity_type, $bundles[$index]); + foreach ($instances as $field_name => $instance) { + // The field value loaded matches the one inserted. + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index))); + // The value added in hook_field_load() is found. + $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index))); + } + } + + // Check that the single-field load option works. + $entity = field_test_create_stub_entity(1, 1, $bundles[1]); + field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1])); + $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1))); + $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1))); + $this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2]))); + $this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3]))); + } + + /** + * Test saving and loading fields using different storage backends. + */ + function testFieldAttachSaveLoadDifferentStorage() { + $entity_type = 'test_entity'; + $langcode = LANGUAGE_NONE; + + // Create two fields using different storage backends, and their instances. + $fields = array( + array( + 'field_name' => 'field_1', + 'type' => 'test_field', + 'cardinality' => 4, + 'storage' => array('type' => 'field_sql_storage') + ), + array( + 'field_name' => 'field_2', + 'type' => 'test_field', + 'cardinality' => 4, + 'storage' => array('type' => 'field_test_storage') + ), + ); + foreach ($fields as $field) { + field_create_field($field); + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance); + } + + $entity_init = field_test_create_stub_entity(); + + // Create entity and insert random values. + $entity = clone($entity_init); + $values = array(); + foreach ($fields as $field) { + $values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']); + $entity->{$field['field_name']}[$langcode] = $values[$field['field_name']]; + } + field_attach_insert($entity_type, $entity); + + // Check that values are loaded as expected. + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + foreach ($fields as $field) { + $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type']))); + } + } + + /** + * Test storage details alteration. + * + * @see field_test_storage_details_alter() + */ + function testFieldStorageDetailsAlter() { + $field_name = 'field_test_change_my_details'; + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + 'cardinality' => 4, + 'storage' => array('type' => 'field_test_storage'), + ); + $field = field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance); + + $field = field_info_field($instance['field_name']); + $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); + + // The storage details are indexed by a storage engine type. + $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), t('The storage type is Drupal variables.')); + + $details = $field['storage']['details']['drupal_variables']; + + // The field_test storage details are indexed by variable name. The details + // are altered, so moon and mars are correct for this test. + $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.')); + $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.')); + + // Test current and revision storage details together because the columns + // are the same. + foreach ((array) $field['columns'] as $column_name => $attributes) { + $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]'))); + $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]'))); + } + } + + /** + * Tests insert and update with missing or NULL fields. + */ + function testFieldAttachSaveMissingData() { + $entity_type = 'test_entity'; + $entity_init = field_test_create_stub_entity(); + $langcode = LANGUAGE_NONE; + + // Insert: Field is missing. + $entity = clone($entity_init); + field_attach_insert($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved')); + + // Insert: Field is NULL. + field_cache_clear(); + $entity = clone($entity_init); + $entity->{$this->field_name} = NULL; + field_attach_insert($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved')); + + // Add some real data. + field_cache_clear(); + $entity = clone($entity_init); + $values = $this->_generateTestFieldValues(1); + $entity->{$this->field_name}[$langcode] = $values; + field_attach_insert($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); + + // Update: Field is missing. Data should survive. + field_cache_clear(); + $entity = clone($entity_init); + field_attach_update($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place')); + + // Update: Field is NULL. Data should be wiped. + field_cache_clear(); + $entity = clone($entity_init); + $entity->{$this->field_name} = NULL; + field_attach_update($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values')); + + // Re-add some data. + field_cache_clear(); + $entity = clone($entity_init); + $values = $this->_generateTestFieldValues(1); + $entity->{$this->field_name}[$langcode] = $values; + field_attach_update($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); + + // Update: Field is empty array. Data should be wiped. + field_cache_clear(); + $entity = clone($entity_init); + $entity->{$this->field_name} = array(); + field_attach_update($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values')); + } + + /** + * Test insert with missing or NULL fields, with default value. + */ + function testFieldAttachSaveMissingDataDefaultValue() { + // Add a default value function. + $this->instance['default_value_function'] = 'field_test_default_value'; + field_update_instance($this->instance); + + $entity_type = 'test_entity'; + $entity_init = field_test_create_stub_entity(); + $langcode = LANGUAGE_NONE; + + // Insert: Field is NULL. + $entity = clone($entity_init); + $entity->{$this->field_name}[$langcode] = NULL; + field_attach_insert($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved')); + + // Insert: Field is missing. + field_cache_clear(); + $entity = clone($entity_init); + field_attach_insert($entity_type, $entity); + + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance); + $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved')); + } + + /** + * Test field_attach_delete(). + */ + function testFieldAttachDelete() { + $entity_type = 'test_entity'; + $langcode = LANGUAGE_NONE; + $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + + // Create revision 0 + $values = $this->_generateTestFieldValues($this->field['cardinality']); + $rev[0]->{$this->field_name}[$langcode] = $values; + field_attach_insert($entity_type, $rev[0]); + + // Create revision 1 + $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']); + $rev[1]->{$this->field_name}[$langcode] = $values; + field_attach_update($entity_type, $rev[1]); + + // Create revision 2 + $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']); + $rev[2]->{$this->field_name}[$langcode] = $values; + field_attach_update($entity_type, $rev[2]); + + // Confirm each revision loads + foreach (array_keys($rev) as $vid) { + $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $read)); + $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values."); + } + + // Delete revision 1, confirm the other two still load. + field_attach_delete_revision($entity_type, $rev[1]); + foreach (array(0, 2) as $vid) { + $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $read)); + $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values."); + } + + // Confirm the current revision still loads + $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $read)); + $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity current revision has {$this->field['cardinality']} values."); + + // Delete all field data, confirm nothing loads + field_attach_delete($entity_type, $rev[2]); + foreach (array(0, 1, 2) as $vid) { + $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); + field_attach_load_revision($entity_type, array(0 => $read)); + $this->assertIdentical($read->{$this->field_name}, array(), "The test entity revision $vid is deleted."); + } + $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $read)); + $this->assertIdentical($read->{$this->field_name}, array(), t('The test entity current revision is deleted.')); + } + + /** + * Test field_attach_create_bundle() and field_attach_rename_bundle(). + */ + function testFieldAttachCreateRenameBundle() { + // Create a new bundle. This has to be initiated by the module so that its + // hook_entity_info() is consistent. + $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); + field_test_create_bundle($new_bundle); + + // Add an instance to that bundle. + $this->instance['bundle'] = $new_bundle; + field_create_instance($this->instance); + + // Save an entity with data in the field. + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; + $values = $this->_generateTestFieldValues($this->field['cardinality']); + $entity->{$this->field_name}[$langcode] = $values; + $entity_type = 'test_entity'; + field_attach_insert($entity_type, $entity); + + // Verify the field data is present on load. + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Data is retrieved for the new bundle"); + + // Rename the bundle. This has to be initiated by the module so that its + // hook_entity_info() is consistent. + $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); + field_test_rename_bundle($this->instance['bundle'], $new_bundle); + + // Check that the instance definition has been updated. + $this->instance = field_info_instance($entity_type, $this->field_name, $new_bundle); + $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance."); + + // Verify the field data is present on load. + $entity = field_test_create_stub_entity(0, 0, $new_bundle); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage"); + } + + /** + * Test field_attach_delete_bundle(). + */ + function testFieldAttachDeleteBundle() { + // Create a new bundle. This has to be initiated by the module so that its + // hook_entity_info() is consistent. + $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); + field_test_create_bundle($new_bundle); + + // Add an instance to that bundle. + $this->instance['bundle'] = $new_bundle; + field_create_instance($this->instance); + + // Create a second field for the test bundle + $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1); + field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'test_entity', + 'bundle' => $this->instance['bundle'], + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + // test_field has no instance settings + 'widget' => array( + 'type' => 'test_field_widget', + 'settings' => array( + 'size' => mt_rand(0, 255)))); + field_create_instance($instance); + + // Save an entity with data for both fields + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; + $values = $this->_generateTestFieldValues($this->field['cardinality']); + $entity->{$this->field_name}[$langcode] = $values; + $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues(1); + field_attach_insert('test_entity', $entity); + + // Verify the fields are present on load + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + field_attach_load('test_entity', array(0 => $entity)); + $this->assertEqual(count($entity->{$this->field_name}[$langcode]), 4, 'First field got loaded'); + $this->assertEqual(count($entity->{$field_name}[$langcode]), 1, 'Second field got loaded'); + + // Delete the bundle. This has to be initiated by the module so that its + // hook_entity_info() is consistent. + field_test_delete_bundle($this->instance['bundle']); + + // Verify no data gets loaded + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + field_attach_load('test_entity', array(0 => $entity)); + $this->assertFalse(isset($entity->{$this->field_name}[$langcode]), 'No data for first field'); + $this->assertFalse(isset($entity->{$field_name}[$langcode]), 'No data for second field'); + + // Verify that the instances are gone + $this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted"); + $this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted"); + } +} + +/** + * Unit test class for non-storage related field_attach_* functions. + */ +class FieldAttachOtherTestCase extends FieldAttachTestCase { + public static function getInfo() { + return array( + 'name' => 'Field attach tests (other)', + 'description' => 'Test other Field Attach API functions.', + 'group' => 'Field API', + ); + } + + /** + * Test field_attach_view() and field_attach_prepare_view(). + */ + function testFieldAttachView() { + $entity_type = 'test_entity'; + $entity_init = field_test_create_stub_entity(); + $langcode = LANGUAGE_NONE; + + // Populate values to be displayed. + $values = $this->_generateTestFieldValues($this->field['cardinality']); + $entity_init->{$this->field_name}[$langcode] = $values; + + // Simple formatter, label displayed. + $entity = clone($entity_init); + $formatter_setting = $this->randomName(); + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $formatter_setting, + ) + ), + ); + field_update_instance($this->instance); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); + $output = drupal_render($entity->content); + $this->content = $output; + $this->assertRaw($this->instance['label'], "Label is displayed."); + foreach ($values as $delta => $value) { + $this->content = $output; + $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); + } + + // Label hidden. + $entity = clone($entity_init); + $this->instance['display']['full']['label'] = 'hidden'; + field_update_instance($this->instance); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); + $output = drupal_render($entity->content); + $this->content = $output; + $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); + + // Field hidden. + $entity = clone($entity_init); + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'hidden', + ), + ); + field_update_instance($this->instance); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); + $output = drupal_render($entity->content); + $this->content = $output; + $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); + foreach ($values as $delta => $value) { + $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); + } + + // Multiple formatter. + $entity = clone($entity_init); + $formatter_setting = $this->randomName(); + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_multiple', + 'settings' => array( + 'test_formatter_setting_multiple' => $formatter_setting, + ) + ), + ); + field_update_instance($this->instance); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); + $output = drupal_render($entity->content); + $display = $formatter_setting; + foreach ($values as $delta => $value) { + $display .= "|$delta:{$value['value']}"; + } + $this->content = $output; + $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied."); + + // Test a formatter that uses hook_field_formatter_prepare_view(). + $entity = clone($entity_init); + $formatter_setting = $this->randomName(); + $this->instance['display'] = array( + 'full' => array( + 'label' => 'above', + 'type' => 'field_test_with_prepare_view', + 'settings' => array( + 'test_formatter_setting_additional' => $formatter_setting, + ) + ), + ); + field_update_instance($this->instance); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); + $output = drupal_render($entity->content); + $this->content = $output; + foreach ($values as $delta => $value) { + $this->content = $output; + $expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1); + $this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied."); + } + + // TODO: + // - check display order with several fields + + // Preprocess template. + $variables = array(); + field_attach_preprocess($entity_type, $entity, $entity->content, $variables); + $result = TRUE; + foreach ($values as $delta => $item) { + if ($variables[$this->field_name][$delta]['value'] !== $item['value']) { + $result = FALSE; + break; + } + } + $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name))); + } + + /** + * Test field cache. + */ + function testFieldAttachCache() { + // Initialize random values and a test entity. + $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; + $values = $this->_generateTestFieldValues($this->field['cardinality']); + + // Non-cacheable entity type. + $entity_type = 'test_entity'; + $cid = "field:$entity_type:{$entity_init->ftid}"; + + // Check that no initial cache entry is present. + $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no initial cache entry')); + + // Save, and check that no cache entry is present. + $entity = clone($entity_init); + $entity->{$this->field_name}[$langcode] = $values; + field_attach_insert($entity_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert')); + + // Load, and check that no cache entry is present. + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on load')); + + + // Cacheable entity type. + $entity_type = 'test_cacheable_entity'; + $cid = "field:$entity_type:{$entity_init->ftid}"; + $instance = $this->instance; + $instance['entity_type'] = $entity_type; + field_create_instance($instance); + + // Check that no initial cache entry is present. + $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no initial cache entry')); + + // Save, and check that no cache entry is present. + $entity = clone($entity_init); + $entity->{$this->field_name}[$langcode] = $values; + field_attach_insert($entity_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert')); + + // Load a single field, and check that no cache entry is present. + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id)); + $cache = cache_get($cid, 'cache_field'); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on loading a single field')); + + // Load, and check that a cache entry is present with the expected values. + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $cache = cache_get($cid, 'cache_field'); + $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); + + // Update with different values, and check that the cache entry is wiped. + $values = $this->_generateTestFieldValues($this->field['cardinality']); + $entity = clone($entity_init); + $entity->{$this->field_name}[$langcode] = $values; + field_attach_update($entity_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update')); + + // Load, and check that a cache entry is present with the expected values. + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $cache = cache_get($cid, 'cache_field'); + $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); + + // Create a new revision, and check that the cache entry is wiped. + $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']); + $values = $this->_generateTestFieldValues($this->field['cardinality']); + $entity = clone($entity_init); + $entity->{$this->field_name}[$langcode] = $values; + field_attach_update($entity_type, $entity); + $cache = cache_get($cid, 'cache_field'); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation')); + + // Load, and check that a cache entry is present with the expected values. + $entity = clone($entity_init); + field_attach_load($entity_type, array($entity->ftid => $entity)); + $cache = cache_get($cid, 'cache_field'); + $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); + + // Delete, and check that the cache entry is wiped. + field_attach_delete($entity_type, $entity); + $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry after delete')); + } + + /** + * Test field_attach_validate(). + * + * Verify that field_attach_validate() invokes the correct + * hook_field_validate. + */ + function testFieldAttachValidate() { + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + $langcode = LANGUAGE_NONE; + + // Set up values to generate errors + $values = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = -1; + } + // Arrange for item 1 not to generate an error + $values[1]['value'] = 1; + $entity->{$this->field_name}[$langcode] = $values; + + try { + field_attach_validate($entity_type, $entity); + } + catch (FieldValidationException $e) { + $errors = $e->errors; + } + + foreach ($values as $delta => $value) { + if ($value['value'] != 1) { + $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); + $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta"); + unset($errors[$this->field_name][$langcode][$delta]); + } + else { + $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta"); + } + } + $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set'); + + // Check that cardinality is validated. + $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); + try { + field_attach_validate($entity_type, $entity); + } + catch (FieldValidationException $e) { + $errors = $e->errors; + } + $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.')); + + } + + /** + * Test field_attach_form(). + * + * This could be much more thorough, but it does verify that the correct + * widgets show up. + */ + function testFieldAttachForm() { + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + + $form = array(); + $form_state = form_state_defaults(); + field_attach_form($entity_type, $entity, $form, $form_state); + + $langcode = LANGUAGE_NONE; + $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + // field_test_widget uses 'textfield' + $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); + } + } + + /** + * Test field_attach_submit(). + */ + function testFieldAttachSubmit() { + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + + // Build the form. + $form = array(); + $form_state = form_state_defaults(); + field_attach_form($entity_type, $entity, $form, $form_state); + + // Simulate incoming values. + $values = array(); + $weights = array(); + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$delta]['value'] = mt_rand(1, 127); + // Assign random weight. + do { + $weight = mt_rand(0, $this->field['cardinality']); + } while (in_array($weight, $weights)); + $weights[$delta] = $weight; + $values[$delta]['_weight'] = $weight; + } + // Leave an empty value. 'field_test' fields are empty if empty(). + $values[1]['value'] = 0; + + $langcode = LANGUAGE_NONE; + // Pretend the form has been built. + drupal_prepare_form('field_test_entity_form', $form, $form_state); + drupal_process_form('field_test_entity_form', $form, $form_state); + $form_state['values'][$this->field_name][$langcode] = $values; + field_attach_submit($entity_type, $entity, $form, $form_state); + + asort($weights); + $expected_values = array(); + foreach ($weights as $key => $value) { + if ($key != 1) { + $expected_values[] = array('value' => $values[$key]['value']); + } + } + $this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values'); + } +} + +class FieldInfoTestCase extends FieldTestCase { + + public static function getInfo() { + return array( + 'name' => 'Field info tests', + 'description' => 'Get information about existing fields, instances and bundles.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp('field_test'); + } + + /** + * Test that field types and field definitions are correcly cached. + */ + function testFieldInfo() { + // Test that field_test module's fields, widgets, and formatters show up. + $field_test_info = field_test_field_info(); + $formatter_info = field_test_field_formatter_info(); + $widget_info = field_test_field_widget_info(); + $storage_info = field_test_field_storage_info(); + + $info = field_info_field_types(); + foreach ($field_test_info as $t_key => $field_type) { + foreach ($field_type as $key => $val) { + $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val")); + } + $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears")); + } + + $info = field_info_formatter_types(); + foreach ($formatter_info as $f_key => $formatter) { + foreach ($formatter as $key => $val) { + $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val")); + } + $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears")); + } + + $info = field_info_widget_types(); + foreach ($widget_info as $w_key => $widget) { + foreach ($widget as $key => $val) { + $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val")); + } + $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears")); + } + + $info = field_info_storage_types(); + foreach ($storage_info as $s_key => $storage) { + foreach ($storage as $key => $val) { + $this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val")); + } + $this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears")); + } + + // Verify that no unexpected instances exist. + $core_fields = field_info_fields(); + $instances = field_info_instances('test_entity', 'test_bundle'); + $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.')); + + // Create a field, verify it shows up. + $field = array( + 'field_name' => drupal_strtolower($this->randomName()), + 'type' => 'test_field', + ); + field_create_field($field); + $fields = field_info_fields(); + $this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists')); + $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name')); + $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type')); + $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module')); + $settings = array('test_field_setting' => 'dummy test string'); + foreach ($settings as $key => $val) { + $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val")); + } + $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1')); + $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1')); + + // Create an instance, verify that it shows up + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'label' => $this->randomName(), + 'description' => $this->randomName(), + 'weight' => mt_rand(0, 127), + // test_field has no instance settings + 'widget' => array( + 'type' => 'test_field_widget', + 'settings' => array( + 'test_setting' => 999))); + field_create_instance($instance); + + $instances = field_info_instances('test_entity', $instance['bundle']); + $this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.')); + $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly')); + } + + /** + * Test that cached field definitions are ready for current runtime context. + */ + function testFieldPrepare() { + $field_definition = array( + 'field_name' => 'field', + 'type' => 'test_field', + ); + field_create_field($field_definition); + + // Simulate a stored field definition missing a field setting (e.g. a + // third-party module adding a new field setting has been enabled, and + // existing fields do not know the setting yet). + $data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField(); + $data = unserialize($data); + $data['settings'] = array(); + db_update('field_config') + ->fields(array('data' => serialize($data))) + ->condition('field_name', $field_definition['field_name']) + ->execute(); + + field_cache_clear(); + + // Read the field back. + $field = field_info_field($field_definition['field_name']); + + // Check that all expected settings are in place. + $field_type = field_info_field_types($field_definition['type']); + $this->assertIdentical($field['settings'], $field_type['settings'], t('All expected default field settings are present.')); + } + + /** + * Test that cached instance definitions are ready for current runtime context. + */ + function testInstancePrepare() { + $field_definition = array( + 'field_name' => 'field', + 'type' => 'test_field', + ); + field_create_field($field_definition); + $instance_definition = array( + 'field_name' => $field_definition['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance_definition); + + // Simulate a stored instance definition missing various settings (e.g. a + // third-party module adding instance, widget or display settings has been + // enabled, but existing instances do not know the new settings). + $data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField(); + $data = unserialize($data); + $data['settings'] = array(); + $data['widget']['settings'] = 'unavailable_widget'; + $data['widget']['settings'] = array(); + $data['display']['default']['type'] = 'unavailable_formatter'; + $data['display']['default']['settings'] = array(); + db_update('field_config_instance') + ->fields(array('data' => serialize($data))) + ->condition('field_name', $instance_definition['field_name']) + ->condition('bundle', $instance_definition['bundle']) + ->execute(); + + field_cache_clear(); + + // Read the instance back. + $instance = field_info_instance($instance_definition['entity_type'], $instance_definition['field_name'], $instance_definition['bundle']); + + // Check that all expected instance settings are in place. + $field_type = field_info_field_types($field_definition['type']); + $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('All expected instance settings are present.')); + + // Check that the default widget is used and expected settings are in place. + $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Unavailable widget replaced with default widget.')); + $widget_type = field_info_widget_types($instance['widget']['type']); + $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.')); + + // Check that display settings are set for the 'default' mode. + $display = $instance['display']['default']; + $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode")); + $formatter_type = field_info_formatter_types($display['type']); + $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode")); + } + + /** + * Test that instances on disabled entity types are filtered out. + */ + function testInstanceDisabledEntityType() { + // For this test the field type and the entity type must be exposed by + // different modules. + $field_definition = array( + 'field_name' => 'field', + 'type' => 'test_field', + ); + field_create_field($field_definition); + $instance_definition = array( + 'field_name' => 'field', + 'entity_type' => 'comment', + 'bundle' => 'comment_node_article', + ); + field_create_instance($instance_definition); + + // Disable coment module. This clears field_info cache. + module_disable(array('comment')); + $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.')); + } + + /** + * Test that the field_info settings convenience functions work. + */ + function testSettingsInfo() { + $info = field_test_field_info(); + foreach ($info as $type => $data) { + $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings"); + $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings"); + } + + $info = field_test_field_widget_info(); + foreach ($info as $type => $data) { + $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings"); + } + + $info = field_test_field_formatter_info(); + foreach ($info as $type => $data) { + $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); + } + } +} + +class FieldFormTestCase extends FieldTestCase { + public static function getInfo() { + return array( + 'name' => 'Field form tests', + 'description' => 'Test Field form handling.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp('field_test'); + + $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->drupalLogin($web_user); + + $this->field_single = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field'); + $this->field_multiple = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field', 'cardinality' => 4); + $this->field_unlimited = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED); + + $this->instance = array( + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + } + + function testFieldFormSingle() { + $this->field = $this->field_single; + $this->field_name = $this->field['field_name']; + $this->instance['field_name'] = $this->field_name; + field_create_field($this->field); + field_create_instance($this->instance); + $langcode = LANGUAGE_NONE; + + // Display creation form. + $this->drupalGet('test-entity/add/test-bundle'); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed'); + $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); + // TODO : check that the widget is populated with default value ? + + // Submit with invalid value (field-level validation). + $edit = array("{$this->field_name}[$langcode][0][value]" => -1); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.'); + // TODO : check that the correct field is flagged for error. + + // Create an entity + $value = mt_rand(1, 127); + $edit = array("{$this->field_name}[$langcode][0][value]" => $value); + $this->drupalPost(NULL, $edit, t('Save')); + preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + $id = $match[1]; + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); + $entity = field_test_entity_test_load($id); + $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); + + // Display edit form. + $this->drupalGet('test-entity/' . $id . '/edit'); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", $value, 'Widget is displayed with the correct default value'); + $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); + + // Update the entity. + $value = mt_rand(1, 127); + $edit = array("{$this->field_name}[$langcode][0][value]" => $value); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); + $entity = field_test_entity_test_load($id); + $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was updated'); + + // Empty the field. + $value = ''; + $edit = array("{$this->field_name}[$langcode][0][value]" => $value); + $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); + $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); + $entity = field_test_entity_test_load($id); + $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied'); + + } + + function testFieldFormSingleRequired() { + $this->field = $this->field_single; + $this->field_name = $this->field['field_name']; + $this->instance['field_name'] = $this->field_name; + $this->instance['required'] = TRUE; + field_create_field($this->field); + field_create_instance($this->instance); + $langcode = LANGUAGE_NONE; + + // Submit with missing required value. + $edit = array(); + $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save')); + $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation'); + + // Create an entity + $value = mt_rand(1, 127); + $edit = array("{$this->field_name}[$langcode][0][value]" => $value); + $this->drupalPost(NULL, $edit, t('Save')); + preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + $id = $match[1]; + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); + $entity = field_test_entity_test_load($id); + $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); + + // Edit with missing required value. + $value = ''; + $edit = array("{$this->field_name}[$langcode][0][value]" => $value); + $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); + $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation'); + } + +// function testFieldFormMultiple() { +// $this->field = $this->field_multiple; +// $this->field_name = $this->field['field_name']; +// $this->instance['field_name'] = $this->field_name; +// field_create_field($this->field); +// field_create_instance($this->instance); +// } + + function testFieldFormUnlimited() { + $this->field = $this->field_unlimited; + $this->field_name = $this->field['field_name']; + $this->instance['field_name'] = $this->field_name; + field_create_field($this->field); + field_create_instance($this->instance); + $langcode = LANGUAGE_NONE; + + // Display creation form -> 1 widget. + $this->drupalGet('test-entity/add/test-bundle'); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed'); + $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); + + // Press 'add more' button -> 2 widgets. + $this->drupalPost(NULL, array(), t('Add another item')); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed'); + $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed'); + $this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed'); + // TODO : check that non-field inpurs are preserved ('title')... + + // Yet another time so that we can play with more values -> 3 widgets. + $this->drupalPost(NULL, array(), t('Add another item')); + + // Prepare values and weights. + $count = 3; + $delta_range = $count - 1; + $values = $weights = $pattern = $expected_values = $edit = array(); + for ($delta = 0; $delta <= $delta_range; $delta++) { + // Assign unique random weights. + do { + $weight = mt_rand(-$delta_range, $delta_range); + } while (in_array($weight, $weights)); + $value = mt_rand(1, 127); + $edit["$this->field_name[$langcode][$delta][value]"] = $value; + $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight; + // We'll need three slightly different formats to check the values. + $values[$delta] = $value; + $weights[$delta] = $weight; + $field_values[$weight]['value'] = (string) $value; + $pattern[$weight] = "]*value=\"$value\" [^>]*"; + } + + // Press 'add more' button -> 4 widgets + $this->drupalPost(NULL, $edit, t('Add another item')); + for ($delta = 0; $delta <= $delta_range; $delta++) { + $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); + $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $weights[$delta], "Widget $delta has the right weight"); + } + ksort($pattern); + $pattern = implode('.*', array_values($pattern)); + $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order'); + $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", '', "New widget is displayed"); + $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight"); + $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); + + // Submit the form and create the entity. + $this->drupalPost(NULL, $edit, t('Save')); + preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + $id = $match[1]; + $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); + $entity = field_test_entity_test_load($id); + ksort($field_values); + $field_values = array_values($field_values); + $this->assertIdentical($entity->{$this->field_name}[$langcode], $field_values, 'Field values were saved in the correct order'); + + // Display edit form: check that the expected number of widgets is + // displayed, with correct values change values, reorder, leave an empty + // value in the middle. + // Submit: check that the entity is updated with correct values + // Re-submit: check that the field can be emptied. + + // Test with several multiple fields in a form + } + + function testFieldFormJSAddMore() { + $this->field = $this->field_unlimited; + $this->field_name = $this->field['field_name']; + $this->instance['field_name'] = $this->field_name; + field_create_field($this->field); + field_create_instance($this->instance); + $langcode = LANGUAGE_NONE; + + // Display creation form -> 1 widget. + $this->drupalGet('test-entity/add/test-bundle'); + + // Press 'add more' button a couple times -> 3 widgets. + // drupalPostAJAX() will not work iteratively, so we add those through + // non-JS submission. + $this->drupalPost(NULL, array(), t('Add another item')); + $this->drupalPost(NULL, array(), t('Add another item')); + + // Prepare values and weights. + $count = 3; + $delta_range = $count - 1; + $values = $weights = $pattern = $expected_values = $edit = array(); + for ($delta = 0; $delta <= $delta_range; $delta++) { + // Assign unique random weights. + do { + $weight = mt_rand(-$delta_range, $delta_range); + } while (in_array($weight, $weights)); + $value = mt_rand(1, 127); + $edit["$this->field_name[$langcode][$delta][value]"] = $value; + $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight; + // We'll need three slightly different formats to check the values. + $values[$delta] = $value; + $weights[$delta] = $weight; + $field_values[$weight]['value'] = (string) $value; + $pattern[$weight] = "]*value=\"$value\" [^>]*"; + } + // Press 'add more' button through AJAX, and place the expected HTML result + // as the tested content. + $commands = $this->drupalPostAJAX(NULL, $edit, $this->field_name . '_add_more'); + $this->content = $commands[1]['data']; + + for ($delta = 0; $delta <= $delta_range; $delta++) { + $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); + $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $weights[$delta], "Widget $delta has the right weight"); + } + ksort($pattern); + $pattern = implode('.*', array_values($pattern)); + $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order'); + $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", '', "New widget is displayed"); + $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight"); + $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); + } + + /** + * Tests widgets handling multiple values. + */ + function testFieldFormMultipleWidget() { + // Create a field with fixed cardinality and an instance using a multiple + // widget. + $this->field = $this->field_multiple; + $this->field_name = $this->field['field_name']; + $this->instance['field_name'] = $this->field_name; + $this->instance['widget']['type'] = 'test_field_widget_multiple'; + field_create_field($this->field); + field_create_instance($this->instance); + $langcode = LANGUAGE_NONE; + + // Display creation form. + $this->drupalGet('test-entity/add/test-bundle'); + $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed.')); + + // Create entity with three values. + $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3'); + $this->drupalPost(NULL, $edit, t('Save')); + preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + $id = $match[1]; + + // Check that the values were saved. + $entity_init = field_test_create_stub_entity($id); + $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); + + // Display the form, check that the values are correctly filled in. + $this->drupalGet('test-entity/' . $id . '/edit'); + $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', t('Widget is displayed.')); + + // Submit the form with more values than the field accepts. + $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3, 4, 5'); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw('this field cannot hold more than 4 values', t('Form validation failed.')); + // Check that the field values were not submitted. + $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); + } + + /** + * Tests fields with no 'edit' access. + */ + function testFieldFormAccess() { + // Create a "regular" field. + $field = $this->field_single; + $field_name = $field['field_name']; + $instance = $this->instance; + $instance['field_name'] = $field_name; + field_create_field($field); + field_create_instance($instance); + + // Create a field with no edit access - see field_test_field_access(). + $field_no_access = array( + 'field_name' => 'field_no_edit_access', + 'type' => 'test_field', + ); + $field_name_no_access = $field_no_access['field_name']; + $instance_no_access = array( + 'field_name' => $field_name_no_access, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'default_value' => array(0 => array('value' => 99)), + ); + field_create_field($field_no_access); + field_create_instance($instance_no_access); + + $langcode = LANGUAGE_NONE; + + // Display creation form. + $this->drupalGet('test-entity/add/test-bundle'); + $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', t('Widget is not displayed if field access is denied.')); + + // Create entity. + $edit = array("{$field_name}[$langcode][0][value]" => 1); + $this->drupalPost(NULL, $edit, t('Save')); + preg_match('|test-entity/(\d+)/edit|', $this->url, $match); + $id = $match[1]; + + // Check that the default value was saved. + $entity = field_test_entity_test_load($id); + $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('Default value was saved for the field with no edit access.')); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, t('Entered value vas saved for the field with edit access.')); + + // Create a new revision. + $edit = array("{$field_name}[$langcode][0][value]" => 2, 'revision' => TRUE); + $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); + + // Check that the new revision has the expected values. + $entity = field_test_entity_test_load($id); + $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); + + // Check that the revision is also saved in the revisions table. + $entity = field_test_entity_test_load($id, $entity->ftvid); + $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); + $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); + } +} + +class FieldDisplayAPITestCase extends FieldTestCase { + public static function getInfo() { + return array( + 'name' => 'Field Display API tests', + 'description' => 'Test the display API.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp('field_test'); + + // Create a field and instance. + $this->field_name = 'test_field'; + $this->label = $this->randomName(); + $this->cardinality = 4; + + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'test_field', + 'cardinality' => $this->cardinality, + ); + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'label' => $this->label, + 'display' => array( + 'default' => array( + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $this->randomName(), + ), + ), + 'teaser' => array( + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $this->randomName(), + ), + ), + ), + ); + field_create_field($this->field); + field_create_instance($this->instance); + + // Create an entity with values. + $this->values = $this->_generateTestFieldValues($this->cardinality); + $this->entity = field_test_create_stub_entity(); + $this->is_new = TRUE; + $this->entity->{$this->field_name}[LANGUAGE_NONE] = $this->values; + field_test_entity_save($this->entity); + } + + /** + * Test the field_view_field() function. + */ + function testFieldViewField() { + // No display settings: check that default display settings are used. + $output = field_view_field('test_entity', $this->entity, $this->field_name); + $this->drupalSetContent(drupal_render($output)); + $settings = field_info_formatter_settings('field_test_default'); + $setting = $settings['test_formatter_setting']; + $this->assertText($this->label, t('Label was displayed.')); + foreach($this->values as $delta => $value) { + $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // Check that explicit display settings are used. + $display = array( + 'label' => 'hidden', + 'type' => 'field_test_multiple', + 'settings' => array( + 'test_formatter_setting_multiple' => $this->randomName(), + ), + ); + $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); + $this->drupalSetContent(drupal_render($output)); + $setting = $display['settings']['test_formatter_setting_multiple']; + $this->assertNoText($this->label, t('Label was not displayed.')); + $array = array(); + foreach($this->values as $delta => $value) { + $array[] = $delta . ':' . $value['value']; + } + $this->assertText($setting . '|' . implode('|', $array), t('Values were displayed with expected setting.')); + + // Check the prepare_view steps are invoked. + $display = array( + 'label' => 'hidden', + 'type' => 'field_test_with_prepare_view', + 'settings' => array( + 'test_formatter_setting_additional' => $this->randomName(), + ), + ); + $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); + $view = drupal_render($output); + $this->drupalSetContent($view); + $setting = $display['settings']['test_formatter_setting_additional']; + $this->assertNoText($this->label, t('Label was not displayed.')); + foreach ($this->values as $delta => $value) { + $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // View mode: check that display settings specified in the instance are + // used. + $output = field_view_field('test_entity', $this->entity, $this->field_name, 'teaser'); + $this->drupalSetContent(drupal_render($output)); + $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; + $this->assertText($this->label, t('Label was displayed.')); + foreach($this->values as $delta => $value) { + $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // Unknown view mode: check that display settings for 'default' view mode + // are used. + $output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_view_mode'); + $this->drupalSetContent(drupal_render($output)); + $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; + $this->assertText($this->label, t('Label was displayed.')); + foreach($this->values as $delta => $value) { + $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + } + + /** + * Test the field_view_value() function. + */ + function testFieldViewValue() { + // No display settings: check that default display settings are used. + $settings = field_info_formatter_settings('field_test_default'); + $setting = $settings['test_formatter_setting']; + foreach ($this->values as $delta => $value) { + $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; + $output = field_view_value('test_entity', $this->entity, $this->field_name, $item); + $this->drupalSetContent(drupal_render($output)); + $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // Check that explicit display settings are used. + $display = array( + 'type' => 'field_test_multiple', + 'settings' => array( + 'test_formatter_setting_multiple' => $this->randomName(), + ), + ); + $setting = $display['settings']['test_formatter_setting_multiple']; + $array = array(); + foreach ($this->values as $delta => $value) { + $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; + $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); + $this->drupalSetContent(drupal_render($output)); + $this->assertText($setting . '|0:' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // Check that prepare_view steps are invoked. + $display = array( + 'type' => 'field_test_with_prepare_view', + 'settings' => array( + 'test_formatter_setting_additional' => $this->randomName(), + ), + ); + $setting = $display['settings']['test_formatter_setting_additional']; + $array = array(); + foreach ($this->values as $delta => $value) { + $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; + $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); + $this->drupalSetContent(drupal_render($output)); + $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // View mode: check that display settings specified in the instance are + // used. + $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; + foreach ($this->values as $delta => $value) { + $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; + $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'teaser'); + $this->drupalSetContent(drupal_render($output)); + $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + + // Unknown view mode: check that display settings for 'default' view mode + // are used. + $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; + foreach ($this->values as $delta => $value) { + $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; + $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'unknown_view_mode'); + $this->drupalSetContent(drupal_render($output)); + $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); + } + } +} + +class FieldCrudTestCase extends FieldTestCase { + public static function getInfo() { + return array( + 'name' => 'Field CRUD tests', + 'description' => 'Test field create, read, update, and delete.', + 'group' => 'Field API', + ); + } + + function setUp() { + // field_update_field() tests use number.module + parent::setUp('field_test', 'number'); + } + + // TODO : test creation with + // - a full fledged $field structure, check that all the values are there + // - a minimal $field structure, check all default values are set + // defer actual $field comparison to a helper function, used for the two cases above + + /** + * Test the creation of a field. + */ + function testCreateField() { + $field_definition = array( + 'field_name' => 'field_2', + 'type' => 'test_field', + ); + field_test_memorize(); + $field_definition = field_create_field($field_definition); + $mem = field_test_memorize(); + $this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, 'hook_field_create_field() called with correct arguments.'); + + // Read the raw record from the {field_config_instance} table. + $result = db_query('SELECT * FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name'])); + $record = $result->fetchAssoc(); + $record['data'] = unserialize($record['data']); + + // Ensure that basic properties are preserved. + $this->assertEqual($record['field_name'], $field_definition['field_name'], t('The field name is properly saved.')); + $this->assertEqual($record['type'], $field_definition['type'], t('The field type is properly saved.')); + + // Ensure that cardinality defaults to 1. + $this->assertEqual($record['cardinality'], 1, t('Cardinality defaults to 1.')); + + // Ensure that default settings are present. + $field_type = field_info_field_types($field_definition['type']); + $this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.')); + + // Ensure that default storage was set. + $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.')); + + // Guarantee that the name is unique. + try { + field_create_field($field_definition); + $this->fail(t('Cannot create two fields with the same name.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create two fields with the same name.')); + } + + // Check that field type is required. + try { + $field_definition = array( + 'field_name' => 'field_1', + ); + field_create_field($field_definition); + $this->fail(t('Cannot create a field with no type.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create a field with no type.')); + } + + // Check that field name is required. + try { + $field_definition = array( + 'type' => 'test_field' + ); + field_create_field($field_definition); + $this->fail(t('Cannot create an unnamed field.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create an unnamed field.')); + } + + // Check that field name must start with a letter or _. + try { + $field_definition = array( + 'field_name' => '2field_2', + 'type' => 'test_field', + ); + field_create_field($field_definition); + $this->fail(t('Cannot create a field with a name starting with a digit.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create a field with a name starting with a digit.')); + } + + // Check that field name must only contain lowercase alphanumeric or _. + try { + $field_definition = array( + 'field_name' => 'field#_3', + 'type' => 'test_field', + ); + field_create_field($field_definition); + $this->fail(t('Cannot create a field with a name containing an illegal character.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create a field with a name containing an illegal character.')); + } + + // Check that field name cannot be longer than 32 characters long. + try { + $field_definition = array( + 'field_name' => '_12345678901234567890123456789012', + 'type' => 'test_field', + ); + field_create_field($field_definition); + $this->fail(t('Cannot create a field with a name longer than 32 characters.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create a field with a name longer than 32 characters.')); + } + + // Check that field name can not be an entity key. + // "ftvid" is known as an entity key from the "test_entity" type. + try { + $field_definition = array( + 'type' => 'test_field', + 'field_name' => 'ftvid', + ); + $field = field_create_field($field_definition); + $this->fail(t('Cannot create a field bearing the name of an entity key.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create a field bearing the name of an entity key.')); + } + } + + /** + * Test failure to create a field. + */ + function testCreateFieldFail() { + $field_name = 'duplicate'; + $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure')); + $query = db_select('field_config')->condition('field_name', $field_name)->countQuery(); + + // The field does not appear in field_config. + $count = $query->execute()->fetchField(); + $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); + + // Try to create the field. + try { + $field = field_create_field($field_definition); + $this->assertTrue(FALSE, 'Field creation (correctly) fails.'); + } + catch (Exception $e) { + $this->assertTrue(TRUE, 'Field creation (correctly) fails.'); + } + + // The field does not appear in field_config. + $count = $query->execute()->fetchField(); + $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); + } + + /** + * Test reading back a field definition. + */ + function testReadField() { + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ); + field_create_field($field_definition); + + // Read the field back. + $field = field_read_field($field_definition['field_name']); + $this->assertTrue($field_definition < $field, t('The field was properly read.')); + } + + /** + * Test creation of indexes on data column. + */ + function testFieldIndexes() { + // Check that indexes specified by the field type are used by default. + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ); + field_create_field($field_definition); + $field = field_read_field($field_definition['field_name']); + $expected_indexes = array('value' => array('value')); + $this->assertEqual($field['indexes'], $expected_indexes, t('Field type indexes saved by default')); + + // Check that indexes specified by the field definition override the field + // type indexes. + $field_definition = array( + 'field_name' => 'field_2', + 'type' => 'test_field', + 'indexes' => array( + 'value' => array(), + ), + ); + field_create_field($field_definition); + $field = field_read_field($field_definition['field_name']); + $expected_indexes = array('value' => array()); + $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes override field type indexes')); + + // Check that indexes specified by the field definition add to the field + // type indexes. + $field_definition = array( + 'field_name' => 'field_3', + 'type' => 'test_field', + 'indexes' => array( + 'value_2' => array('value'), + ), + ); + field_create_field($field_definition); + $field = field_read_field($field_definition['field_name']); + $expected_indexes = array('value' => array('value'), 'value_2' => array('value')); + $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes are merged with field type indexes')); + } + + /** + * Test the deletion of a field. + */ + function testDeleteField() { + // TODO: Also test deletion of the data stored in the field ? + + // Create two fields (so we can test that only one is deleted). + $this->field = array('field_name' => 'field_1', 'type' => 'test_field'); + field_create_field($this->field); + $this->another_field = array('field_name' => 'field_2', 'type' => 'test_field'); + field_create_field($this->another_field); + + // Create instances for each. + $this->instance_definition = array( + 'field_name' => $this->field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'widget' => array( + 'type' => 'test_field_widget', + ), + ); + field_create_instance($this->instance_definition); + $this->another_instance_definition = $this->instance_definition; + $this->another_instance_definition['field_name'] = $this->another_field['field_name']; + field_create_instance($this->another_instance_definition); + + // Test that the first field is not deleted, and then delete it. + $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.')); + field_delete_field($this->field['field_name']); + + // Make sure that the field is marked as deleted when it is specifically + // loaded. + $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.')); + + // Make sure that this field's instance is marked as deleted when it is + // specifically loaded. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.')); + + // Try to load the field normally and make sure it does not show up. + $field = field_read_field($this->field['field_name']); + $this->assertTrue(empty($field), t('A deleted field is not loaded by default.')); + + // Try to load the instance normally and make sure it does not show up. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.')); + + // Make sure the other field (and its field instance) are not deleted. + $another_field = field_read_field($this->another_field['field_name']); + $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.')); + $another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); + $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.')); + + // Try to create a new field the same name as a deleted field and + // write data into it. + field_create_field($this->field); + field_create_instance($this->instance_definition); + $field = field_read_field($this->field['field_name']); + $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field with a previously used name is created.')); + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new instance for a previously used field name is created.')); + + // Save an entity with data for the field + $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + $langcode = LANGUAGE_NONE; + $values[0]['value'] = mt_rand(1, 127); + $entity->{$field['field_name']}[$langcode] = $values; + $entity_type = 'test_entity'; + field_attach_insert('test_entity', $entity); + + // Verify the field is present on load + $entity = field_test_create_stub_entity(0, 0, $this->instance_definition['bundle']); + field_attach_load($entity_type, array(0 => $entity)); + $this->assertIdentical(count($entity->{$field['field_name']}[$langcode]), count($values), "Data in previously deleted field saves and loads correctly"); + foreach ($values as $delta => $value) { + $this->assertEqual($entity->{$field['field_name']}[$langcode][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly"); + } + } + + function testUpdateNonExistentField() { + $test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal'); + try { + field_update_field($test_field); + $this->fail(t('Cannot update a field that does not exist.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot update a field that does not exist.')); + } + } + + function testUpdateFieldType() { + $field = array('field_name' => 'field_type', 'type' => 'number_decimal'); + $field = field_create_field($field); + + $test_field = array('field_name' => 'field_type', 'type' => 'number_integer'); + try { + field_update_field($test_field); + $this->fail(t('Cannot update a field to a different type.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot update a field to a different type.')); + } + } + + /** + * Test updating a field. + */ + function testUpdateField() { + // Create a decimal 5.2 field. + $field = array('field_name' => 'decimal53', 'type' => 'number_decimal', 'cardinality' => 3, 'settings' => array('precision' => 5, 'scale' => 2)); + $field = field_create_field($field); + $instance = array('field_name' => 'decimal53', 'entity_type' => 'test_entity', 'bundle' => 'test_bundle'); + $instance = field_create_instance($instance); + + // Update it to a deciaml 5.3 field. + $field['settings']['scale'] = 3; + field_update_field($field); + + // Save values with 2, 3, and 4 decimal places. + $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + $entity->decimal53[LANGUAGE_NONE][0]['value'] = '1.23'; + $entity->decimal53[LANGUAGE_NONE][1]['value'] = '1.235'; + $entity->decimal53[LANGUAGE_NONE][2]['value'] = '1.2355'; + field_attach_insert('test_entity', $entity); + $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + + // Verify that the updated 5.3 field rounds to 3 decimal places. + field_attach_load('test_entity', array(0 => $entity)); + $this->assertEqual($entity->decimal53[LANGUAGE_NONE][0]['value'], '1.23', t('2 decimal places are left alone')); + $this->assertEqual($entity->decimal53[LANGUAGE_NONE][1]['value'], '1.235', t('3 decimal places are left alone')); + $this->assertEqual($entity->decimal53[LANGUAGE_NONE][2]['value'], '1.236', t('4 decimal places are rounded to 3')); + } + + /** + * Test field type modules forbidding an update. + */ + function testUpdateFieldForbid() { + $field = array('field_name' => 'forbidden', 'type' => 'test_field', 'settings' => array('changeable' => 0, 'unchangeable' => 0)); + $field = field_create_field($field); + $field['settings']['changeable']++; + try { + field_update_field($field); + $this->pass(t("A changeable setting can be updated.")); + } + catch (FieldException $e) { + $this->fail(t("An unchangeable setting cannot be updated.")); + } + $field['settings']['unchangeable']++; + try { + field_update_field($field); + $this->fail(t("An unchangeable setting can be updated.")); + } + catch (FieldException $e) { + $this->pass(t("An unchangeable setting cannot be updated.")); + } + } + + /** + * Test that fields are properly marked active or inactive. + */ + function testActive() { + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + // For this test, we need a storage backend provided by a different + // module than field_test.module. + 'storage' => array( + 'type' => 'field_sql_storage', + ), + ); + field_create_field($field_definition); + + // Test disabling and enabling: + // - the field type module, + // - the storage module, + // - both. + $this->_testActiveHelper($field_definition, array('field_test')); + $this->_testActiveHelper($field_definition, array('field_sql_storage')); + $this->_testActiveHelper($field_definition, array('field_test', 'field_sql_storage')); + } + + /** + * Helper function for testActive(). + * + * Test dependency between a field and a set of modules. + * + * @param $field_definition + * A field definition. + * @param $modules + * An aray of module names. The field will be tested to be inactive as long + * as any of those modules is disabled. + */ + function _testActiveHelper($field_definition, $modules) { + $field_name = $field_definition['field_name']; + + // Read the field. + $field = field_read_field($field_name); + $this->assertTrue($field_definition <= $field, t('The field was properly read.')); + + module_disable($modules, FALSE); + + $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE)); + $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.')); + + // Re-enable modules one by one, and check that the field is still inactive + // while some modules remain disabled. + while ($modules) { + $field = field_read_field($field_name); + $this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules)))); + + $module = array_shift($modules); + module_enable(array($module), FALSE); + } + + // Check that the field is active again after all modules have been + // enabled. + $field = field_read_field($field_name); + $this->assertTrue($field_definition <= $field, t('The field was was marked active.')); + } +} + +class FieldInstanceCrudTestCase extends FieldTestCase { + protected $field; + + public static function getInfo() { + return array( + 'name' => 'Field instance CRUD tests', + 'description' => 'Create field entities by attaching fields to entities.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp('field_test'); + + $this->field = array( + 'field_name' => drupal_strtolower($this->randomName()), + 'type' => 'test_field', + ); + field_create_field($this->field); + $this->instance_definition = array( + 'field_name' => $this->field['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + } + + // TODO : test creation with + // - a full fledged $instance structure, check that all the values are there + // - a minimal $instance structure, check all default values are set + // defer actual $instance comparison to a helper function, used for the two cases above, + // and for testUpdateFieldInstance + + /** + * Test the creation of a field instance. + */ + function testCreateFieldInstance() { + field_create_instance($this->instance_definition); + + // Read the raw record from the {field_config_instance} table. + $result = db_query('SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $this->instance_definition['field_name'], ':bundle' => $this->instance_definition['bundle'])); + $record = $result->fetchAssoc(); + $record['data'] = unserialize($record['data']); + + $field_type = field_info_field_types($this->field['type']); + $widget_type = field_info_widget_types($field_type['default_widget']); + $formatter_type = field_info_formatter_types($field_type['default_formatter']); + + // Check that default values are set. + $this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.')); + $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.')); + $this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.')); + $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.')); + $this->assertTrue(isset($record['data']['display']['default']), t('Display for "full" view_mode has been written.')); + $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.')); + + // Check that default settings are set. + $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.')); + $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.')); + $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_mode have been written.')); + + // Guarantee that the field/bundle combination is unique. + try { + field_create_instance($this->instance_definition); + $this->fail(t('Cannot create two instances with the same field / bundle combination.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create two instances with the same field / bundle combination.')); + } + + // Check that the specified field exists. + try { + $this->instance_definition['field_name'] = $this->randomName(); + field_create_instance($this->instance_definition); + $this->fail(t('Cannot create an instance of a non-existing field.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create an instance of a non-existing field.')); + } + + // Create a field restricted to a specific entity type. + $field_restricted = array( + 'field_name' => drupal_strtolower($this->randomName()), + 'type' => 'test_field', + 'entity_types' => array('test_cacheable_entity'), + ); + field_create_field($field_restricted); + + // Check that an instance can be added to an entity type allowed + // by the field. + try { + $instance = $this->instance_definition; + $instance['field_name'] = $field_restricted['field_name']; + $instance['entity_type'] = 'test_cacheable_entity'; + field_create_instance($instance); + $this->pass(t('Can create an instance on an entity type allowed by the field.')); + } + catch (FieldException $e) { + $this->fail(t('Can create an instance on an entity type allowed by the field.')); + } + + // Check that an instance cannot be added to an entity type + // forbidden by the field. + try { + $instance = $this->instance_definition; + $instance['field_name'] = $field_restricted['field_name']; + field_create_instance($instance); + $this->fail(t('Cannot create an instance on an entity type forbidden by the field.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot create an instance on an entity type forbidden by the field.')); + } + + // TODO: test other failures. + } + + /** + * Test reading back an instance definition. + */ + function testReadFieldInstance() { + field_create_instance($this->instance_definition); + + // Read the instance back. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue($this->instance_definition < $instance, t('The field was properly read.')); + } + + /** + * Test the update of a field instance. + */ + function testUpdateFieldInstance() { + field_create_instance($this->instance_definition); + $field_type = field_info_field_types($this->field['type']); + + // Check that basic changes are saved. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $instance['required'] = !$instance['required']; + $instance['label'] = $this->randomName(); + $instance['description'] = $this->randomName(); + $instance['settings']['test_instance_setting'] = $this->randomName(); + $instance['widget']['settings']['test_widget_setting'] =$this->randomName(); + $instance['widget']['weight']++; + $instance['display']['default']['settings']['test_formatter_setting'] = $this->randomName(); + $instance['display']['default']['weight']++; + field_update_instance($instance); + + $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved')); + $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved')); + $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved')); + $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved')); + $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved')); + $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); + $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], t('Widget weight change is saved')); + + // Check that changing widget and formatter types updates the default settings. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $instance['widget']['type'] = 'test_field_widget_multiple'; + $instance['display']['default']['type'] = 'field_test_multiple'; + field_update_instance($instance); + + $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.')); + $settings = field_info_widget_settings($instance_new['widget']['type']); + $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.')); + $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , t('Formatter type change is saved.')); + $info = field_info_formatter_types($instance_new['display']['default']['type']); + $settings = $info['settings']; + $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , t('Changing formatter type updates default settings.')); + + // Check that adding a new view mode is saved and gets default settings. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $instance['display']['teaser'] = array(); + field_update_instance($instance); + + $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new view_mode has been written.')); + $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new view_mode has been written.')); + $info = field_info_formatter_types($instance_new['display']['teaser']['type']); + $settings = $info['settings']; + $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new view_mode have been written.')); + + // TODO: test failures. + } + + /** + * Test the deletion of a field instance. + */ + function testDeleteFieldInstance() { + // TODO: Test deletion of the data stored in the field also. + // Need to check that data for a 'deleted' field / instance doesn't get loaded + // Need to check data marked deleted is cleaned on cron (not implemented yet...) + + // Create two instances for the same field so we can test that only one + // is deleted. + field_create_instance($this->instance_definition); + $this->another_instance_definition = $this->instance_definition; + $this->another_instance_definition['bundle'] .= '_another_bundle'; + $instance = field_create_instance($this->another_instance_definition); + + // Test that the first instance is not deleted, and then delete it. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.')); + field_delete_instance($instance); + + // Make sure the instance is marked as deleted when the instance is + // specifically loaded. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); + $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.')); + + // Try to load the instance normally and make sure it does not show up. + $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); + $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.')); + + // Make sure the other field instance is not deleted. + $another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); + $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.')); + } +} + +/** + * Unit test class for the multilanguage fields logic. + * + * The following tests will check the multilanguage logic of _field_invoke() and + * that only the correct values are returned by field_available_languages(). + */ +class FieldTranslationsTestCase extends FieldTestCase { + public static function getInfo() { + return array( + 'name' => 'Field translations tests', + 'description' => 'Test multilanguage fields logic.', + 'group' => 'Field API', + ); + } + + function setUp() { + parent::setUp('locale', 'field_test'); + + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + + $this->entity_type = 'test_entity'; + + $field = array( + 'field_name' => $this->field_name, + 'type' => 'test_field', + 'cardinality' => 4, + 'translatable' => TRUE, + ); + field_create_field($field); + $this->field = field_read_field($this->field_name); + + $instance = array( + 'field_name' => $this->field_name, + 'entity_type' => $this->entity_type, + 'bundle' => 'test_bundle', + ); + field_create_instance($instance); + $this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle'); + + require_once DRUPAL_ROOT . '/includes/locale.inc'; + for ($i = 0; $i < 3; ++$i) { + locale_add_language('l' . $i, $this->randomString(), $this->randomString()); + } + } + + /** + * Ensures that only valid values are returned by field_available_languages(). + */ + function testFieldAvailableLanguages() { + // Test 'translatable' fieldable info. + field_test_entity_info_translatable('test_entity', FALSE); + $field = $this->field; + $field['field_name'] .= '_untranslatable'; + + // Enable field translations for the entity. + field_test_entity_info_translatable('test_entity', TRUE); + + // Test hook_field_languages() invocation on a translatable field. + variable_set('field_test_field_available_languages_alter', TRUE); + $enabled_languages = field_content_languages(); + $available_languages = field_available_languages($this->entity_type, $this->field); + foreach ($available_languages as $delta => $langcode) { + if ($langcode != 'xx' && $langcode != 'en') { + $this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode))); + } + } + $this->assertTrue(in_array('xx', $available_languages), t('%language was made available.', array('%language' => 'xx'))); + $this->assertFalse(in_array('en', $available_languages), t('%language was made unavailable.', array('%language' => 'en'))); + + // Test field_available_languages() behavior for untranslatable fields. + $this->field['translatable'] = FALSE; + $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name'); + $available_languages = field_available_languages($this->entity_type, $this->field); + $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.')); + } + + /** + * Test the multilanguage logic of _field_invoke(). + */ + function testFieldInvoke() { + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); + + // Populate some extra languages to check if _field_invoke() correctly uses + // the result of field_available_languages(). + $values = array(); + $extra_languages = mt_rand(1, 4); + $languages = $available_languages = field_available_languages($this->entity_type, $this->field); + for ($i = 0; $i < $extra_languages; ++$i) { + $languages[] = $this->randomString(2); + } + + // For each given language provide some random values. + foreach ($languages as $langcode) { + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$langcode][$delta]['value'] = mt_rand(1, 127); + } + } + $entity->{$this->field_name} = $values; + + $results = _field_invoke('test_op', $entity_type, $entity); + foreach ($results as $langcode => $result) { + $hash = hash('sha256', serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode]))); + // Check whether the parameters passed to _field_invoke() were correctly + // forwarded to the callback function. + $this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode))); + } + $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed.')); + } + + /** + * Test the multilanguage logic of _field_invoke_multiple(). + */ + function testFieldInvokeMultiple() { + $values = array(); + $entities = array(); + $entity_type = 'test_entity'; + $entity_count = mt_rand(1, 5); + $available_languages = field_available_languages($this->entity_type, $this->field); + + for ($id = 1; $id <= $entity_count; ++$id) { + $entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']); + $languages = $available_languages; + + // Populate some extra languages to check whether _field_invoke() + // correctly uses the result of field_available_languages(). + $extra_languages = mt_rand(1, 4); + for ($i = 0; $i < $extra_languages; ++$i) { + $languages[] = $this->randomString(2); + } + + // For each given language provide some random values. + foreach ($languages as $langcode) { + for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { + $values[$id][$langcode][$delta]['value'] = mt_rand(1, 127); + } + } + $entity->{$this->field_name} = $values[$id]; + $entities[$id] = $entity; + } + + $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities); + foreach ($grouped_results as $id => $results) { + foreach ($results as $langcode => $result) { + $hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode]))); + // Check whether the parameters passed to _field_invoke() were correctly + // forwarded to the callback function. + $this->assertEqual($hash, $result, t('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode))); + } + $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for entity %id.', array('%id' => $id))); + } + } + + /** + * Test translatable fields storage/retrieval. + */ + function testTranslatableFieldSaveLoad() { + // Enable field translations for nodes. + field_test_entity_info_translatable('node', TRUE); + $entity_info = entity_get_info('node'); + $this->assertTrue(count($entity_info['translation']), t('Nodes are translatable.')); + + // Prepare the field translations. + field_test_entity_info_translatable('test_entity', TRUE); + $eid = $evid = 1; + $entity_type = 'test_entity'; + $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); + $field_translations = array(); + $available_languages = field_available_languages($entity_type, $this->field); + $this->assertTrue(count($available_languages) > 1, t('Field is translatable.')); + foreach ($available_languages as $langcode) { + $field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']); + } + + // Save and reload the field translations. + $entity->{$this->field_name} = $field_translations; + field_attach_insert($entity_type, $entity); + unset($entity->{$this->field_name}); + field_attach_load($entity_type, array($eid => $entity)); + + // Check if the correct values were saved/loaded. + foreach ($field_translations as $langcode => $items) { + $result = TRUE; + foreach ($items as $delta => $item) { + $result = $result && $item['value'] == $entity->{$this->field_name}[$langcode][$delta]['value']; + } + $this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode))); + } + } + + /** + * Tests display language logic for translatable fields. + */ + function testFieldDisplayLanguage() { + $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $entity_type = 'test_entity'; + + // We need an additional field here to properly test display language + // suggestions. + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + 'cardinality' => 2, + 'translatable' => TRUE, + ); + field_create_field($field); + + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => $entity_type, + 'bundle' => 'test_bundle', + ); + field_create_instance($instance); + + $entity = field_test_create_stub_entity(1, 1, $this->instance['bundle']); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + $instances = field_info_instances($entity_type, $bundle); + + $enabled_languages = field_content_languages(); + $languages = array(); + + // Generate field translations for languages different from the first + // enabled. + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + do { + // Index 0 is reserved for the requested language, this way we ensure + // that no field is actually populated with it. + $langcode = $enabled_languages[mt_rand(1, count($enabled_languages) - 1)]; + } + while (isset($languages[$langcode])); + $languages[$langcode] = TRUE; + $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues($field['cardinality']); + } + + // Test multiple-fields display languages for untranslatable entities. + field_test_entity_info_translatable($entity_type, FALSE); + drupal_static_reset('field_language'); + $requested_language = $enabled_languages[0]; + $display_language = field_language($entity_type, $entity, NULL, $requested_language); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, t('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE))); + } + + // Test multiple-fields display languages for translatable entities. + field_test_entity_info_translatable($entity_type, TRUE); + drupal_static_reset('field_language'); + $display_language = field_language($entity_type, $entity, NULL, $requested_language); + + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $langcode = $display_language[$field_name]; + // As the requested language was not assinged to any field, if the + // returned language is defined for the current field, core fallback rules + // were successfully applied. + $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); + } + + // Test single-field display language. + drupal_static_reset('field_language'); + $langcode = field_language($entity_type, $entity, $this->field_name, $requested_language); + $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); + } + + /** + * Tests field translations when creating a new revision. + */ + function testFieldFormTranslationRevisions() { + $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); + $this->drupalLogin($web_user); + + // Prepare the field translations. + field_test_entity_info_translatable($this->entity_type, TRUE); + $eid = 1; + $entity = field_test_create_stub_entity($eid, $eid, $this->instance['bundle']); + $available_languages = array_flip(field_available_languages($this->entity_type, $this->field)); + unset($available_languages[LANGUAGE_NONE]); + $field_name = $this->field['field_name']; + + // Store the field translations. + $entity->is_new = TRUE; + foreach ($available_languages as $langcode => $value) { + $entity->{$field_name}[$langcode][0]['value'] = $value + 1; + } + field_test_entity_save($entity); + + // Create a new revision. + $langcode = field_valid_language(NULL); + $edit = array("{$field_name}[$langcode][0][value]" => $entity->{$field_name}[$langcode][0]['value'], 'revision' => TRUE); + $this->drupalPost('test-entity/' . $eid . '/edit', $edit, t('Save')); + + // Check translation revisions. + $this->checkTranslationRevisions($eid, $eid, $available_languages); + $this->checkTranslationRevisions($eid, $eid + 1, $available_languages); + } + + /** + * Check if the field translation attached to the entity revision identified + * by the passed arguments were correctly stored. + */ + private function checkTranslationRevisions($eid, $evid, $available_languages) { + $field_name = $this->field['field_name']; + $entity = field_test_entity_test_load($eid, $evid); + foreach ($available_languages as $langcode => $value) { + $passed = isset($entity->{$field_name}[$langcode]) && $entity->{$field_name}[$langcode][0]['value'] == $value + 1; + $this->assertTrue($passed, t('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid))); + } + } +} + +/** + * Unit test class for field bulk delete and batch purge functionality. + */ +class FieldBulkDeleteTestCase extends FieldTestCase { + protected $field; + + public static function getInfo() { + return array( + 'name' => 'Field bulk delete tests', + 'description'=> 'Bulk delete fields and instances, and clean up afterwards.', + 'group' => 'Field API', + ); + } + + /** + * Convenience function for Field API tests. + * + * Given an array of potentially fully-populated entities and an + * optional field name, generate an array of stub entities of the + * same fieldable type which contains the data for the field name + * (if given). + * + * @param $entity_type + * The entity type of $entities. + * @param $entities + * An array of entities of type $entity_type. + * @param $field_name + * Optional; a field name whose data should be copied from + * $entities into the returned stub entities. + * @return + * An array of stub entities corresponding to $entities. + */ + function _generateStubEntities($entity_type, $entities, $field_name = NULL) { + $stubs = array(); + foreach ($entities as $entity) { + $stub = entity_create_stub_entity($entity_type, entity_extract_ids($entity_type, $entity)); + if (isset($field_name)) { + $stub->{$field_name} = $entity->{$field_name}; + } + $stubs[] = $stub; + } + return $stubs; + } + + function setUp() { + parent::setUp('field_test'); + + // Clean up data from previous test cases. + $this->fields = array(); + $this->instances = array(); + + // Create two bundles. + $this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2'); + foreach ($this->bundles as $name => $desc) { + field_test_create_bundle($name, $desc); + } + + // Create two fields. + $field = array('field_name' => 'bf_1', 'type' => 'test_field', 'cardinality' => 1); + $this->fields[] = field_create_field($field); + $field = array('field_name' => 'bf_2', 'type' => 'test_field', 'cardinality' => 4); + $this->fields[] = field_create_field($field); + + // For each bundle, create an instance of each field, and 10 + // entities with values for each field. + $id = 0; + $this->entity_type = 'test_entity'; + foreach ($this->bundles as $bundle) { + foreach ($this->fields as $field) { + $instance = array( + 'field_name' => $field['field_name'], + 'entity_type' => $this->entity_type, + 'bundle' => $bundle, + 'widget' => array( + 'type' => 'test_field_widget', + ) + ); + $this->instances[] = field_create_instance($instance); + } + + for ($i = 0; $i < 10; $i++) { + $entity = field_test_create_stub_entity($id, $id, $bundle); + foreach ($this->fields as $field) { + $entity->{$field['field_name']}[LANGUAGE_NONE] = $this->_generateTestFieldValues($field['cardinality']); + } + $this->entities[$id] = $entity; + field_attach_insert($this->entity_type, $entity); + $id++; + } + } + } + + /** + * Verify that deleting an instance leaves the field data items in + * the database and that the appropriate Field API functions can + * operate on the deleted data and instance. + * + * This tests how EntityFieldQuery interacts with + * field_delete_instance() and could be moved to FieldCrudTestCase, + * but depends on this class's setUp(). + */ + function testDeleteFieldInstance() { + $bundle = reset($this->bundles); + $field = reset($this->fields); + + // There are 10 entities of this bundle. + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->execute(); + $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found before deleting'); + + // Delete the instance. + $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); + field_delete_instance($instance); + + // The instance still exists, deleted. + $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertEqual(count($instances), 1, 'There is one deleted instance'); + $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle'); + + // There are 0 entities of this bundle with non-deleted data. + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->execute(); + $this->assertTrue(!isset($found['test_entity']), 'No entities found after deleting'); + + // There are 10 entities of this bundle when deleted fields are allowed, and + // their values are correct. + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->deleted(TRUE) + ->execute(); + field_attach_load($this->entity_type, $found[$this->entity_type], FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); + $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found after deleting'); + foreach ($found['test_entity'] as $id => $entity) { + $this->assertEqual($this->entities[$id]->{$field['field_name']}, $entity->{$field['field_name']}, "Entity $id with deleted data loaded correctly"); + } + } + + /** + * Verify that field data items and instances are purged when an + * instance is deleted. + */ + function testPurgeInstance() { + field_test_memorize(); + + $bundle = reset($this->bundles); + $field = reset($this->fields); + + // Delete the instance. + $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); + field_delete_instance($instance); + + // No field hooks were called. + $mem = field_test_memorize(); + $this->assertEqual(count($mem), 0, 'No field hooks were called'); + + $batch_size = 2; + for ($count = 8; $count >= 0; $count -= 2) { + // Purge two entities. + field_purge_batch($batch_size); + + // There are $count deleted entities left. + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->deleted(TRUE) + ->execute(); + $this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2'); + } + + // hook_field_delete() was called on a pseudo-entity for each entity. Each + // pseudo entity has a $field property that matches the original entity, + // but no others. + $mem = field_test_memorize(); + $this->assertEqual(count($mem['field_test_field_delete']), 10, 'hook_field_delete was called for the right number of entities'); + $stubs = $this->_generateStubEntities($this->entity_type, $this->entities, $field['field_name']); + $count = count($stubs); + foreach ($mem['field_test_field_delete'] as $args) { + $entity = $args[1]; + $this->assertEqual($stubs[$entity->ftid], $entity, 'hook_field_delete() called with the correct stub'); + unset($stubs[$entity->ftid]); + } + $this->assertEqual(count($stubs), $count-10, 'hook_field_delete was called with each entity once'); + + // The instance still exists, deleted. + $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertEqual(count($instances), 1, 'There is one deleted instance'); + + // Purge the instance. + field_purge_batch($batch_size); + + // The instance is gone. + $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertEqual(count($instances), 0, 'The instance is gone'); + + // The field still exists, not deleted, because it has a second instance. + $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted'); + } + + /** + * Verify that fields are preserved and purged correctly as multiple + * instances are deleted and purged. + */ + function testPurgeField() { + $field = reset($this->fields); + + foreach ($this->bundles as $bundle) { + // Delete the instance. + $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); + field_delete_instance($instance); + + // Purge the data. + field_purge_batch(10); + + // Purge again to purge the instance. + field_purge_batch(0); + + // The field still exists, not deleted, because it was never deleted. + $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted'); + } + + // Delete the field. + field_delete_field($field['field_name']); + + // The field still exists, deleted. + $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertEqual($fields[$field['id']]['deleted'], 1, 'The field exists and is deleted'); + + // Purge the field. + field_purge_batch(0); + + // The field is gone. + $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); + $this->assertEqual(count($fields), 0, 'The field is purged.'); + } +} diff --git modules/field/modules/list/list.test modules/field/modules/list/list.test new file mode 100644 index 0000000..30db6ee --- /dev/null +++ modules/field/modules/list/list.test @@ -0,0 +1,185 @@ + 'List field', + 'description' => 'Test the List field type.', + 'group' => 'Field types', + ); + } + + function setUp() { + parent::setUp('field_test'); + + $this->field_name = 'test_list'; + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'list', + 'cardinality' => 1, + 'settings' => array( + 'allowed_values' => "1|One\n2|Two\n3|Three\n", + ), + ); + $this->field = field_create_field($this->field); + + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'widget' => array( + 'type' => 'options_buttons', + ), + ); + $this->instance = field_create_instance($this->instance); + } + + /** + * Test that allowed values can be updated. + */ + function testUpdateAllowedValues() { + $langcode = LANGUAGE_NONE; + + // All three options appear. + $entity = field_test_create_stub_entity(); + $form = drupal_get_form('field_test_entity_form', $entity); + $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); + + // Removed options do not appear. + $this->field['settings']['allowed_values'] = "2|Two"; + field_update_field($this->field); + $entity = field_test_create_stub_entity(); + $form = drupal_get_form('field_test_entity_form', $entity); + $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); + $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); + + // Completely new options appear. + $this->field['settings']['allowed_values'] = "10|Update\n20|Twenty"; + field_update_field($this->field); + $form = drupal_get_form('field_test_entity_form', $entity); + $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); + $this->assertTrue(empty($form[$this->field_name][$langcode][2]), t('Option 2 does not exist')); + $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), t('Option 10 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), t('Option 20 exists')); + + // Options are reset when a new field with the same name is created. + field_delete_field($this->field_name); + unset($this->field['id']); + $this->field['settings']['allowed_values'] = "1|One\n2|Two\n3|Three\n"; + $this->field = field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + 'widget' => array( + 'type' => 'options_buttons', + ), + ); + $this->instance = field_create_instance($this->instance); + $entity = field_test_create_stub_entity(); + $form = drupal_get_form('field_test_entity_form', $entity); + $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); + $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); + } +} + +/** +* List module UI tests. +*/ +class ListFieldUITestCase extends FieldTestCase { + public static function getInfo() { + return array( + 'name' => 'List field UI', + 'description' => 'Test the List field UI functionality.', + 'group' => 'Field types', + ); + } + + function setUp() { + parent::setUp('field_test', 'field_ui'); + + // Create test user. + $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); + $this->drupalLogin($admin_user); + + // Create content type, with underscores. + $type_name = 'test_' . strtolower($this->randomName()); + $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); + $this->type = $type->type; + // Store a valid URL name, with hyphens instead of underscores. + $this->hyphen_type = str_replace('_', '-', $this->type); + + // Create random field name. + $this->field_label = $this->randomString(); + $this->field_name = strtolower($this->randomName()); + } + + /** + * Tests that allowed values are properly validated in the UI. + */ + function testAllowedValues() { + $element_name = "field[settings][allowed_values]"; + + //Test 'List' field type. + $admin_path = $this->createListFieldAndEdit('list'); + //Check that non-integer keys are rejected. + $edit = array($element_name => "1.1|one\n"); + $this->drupalPost($admin_path, $edit, t('Save settings')); + $this->assertText("keys must be integers", t('Form validation failed.')); + + // Test 'List (number)' field type. + $admin_path = $this->createListFieldAndEdit('list_number'); + //Check that non-numeric keys are rejected. + $edit = array($element_name => "1|one\nB|two"); + $this->drupalPost($admin_path, $edit, t('Save settings')); + $this->assertText("each key must be a valid integer or decimal", t('Form validation failed.')); + + //Test 'List (text)' field type. + $admin_path = $this->createListFieldAndEdit('list_text'); + //Check that over long keys are rejected. + $edit = array($element_name => "1|one\n" . $this->randomName(256) . "|two"); + $this->drupalPost($admin_path, $edit, t('Save settings')); + $this->assertText("each key must be a string at most 255 characters long", t('Form validation failed.')); + } + + /** + * Helper function to create list field of a given type and get the edit page. + * + * @param string $type + * 'list', 'list_boolean', 'list_number', or 'list_text' + */ + private function createListFieldAndEdit($type) { + // Create a test field and instance. + $field_name = 'test_' . $type; + $field = array( + 'field_name' => $field_name, + 'type' => $type, + ); + field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'bundle' => $this->type, + ); + field_create_instance($instance); + + $admin_path = 'admin/structure/types/manage/' . $this->hyphen_type . '/fields/' . $field_name; + return $admin_path; + } + +} + diff --git modules/field/modules/list/tests/list.test modules/field/modules/list/tests/list.test deleted file mode 100644 index 30db6ee..0000000 --- modules/field/modules/list/tests/list.test +++ /dev/null @@ -1,185 +0,0 @@ - 'List field', - 'description' => 'Test the List field type.', - 'group' => 'Field types', - ); - } - - function setUp() { - parent::setUp('field_test'); - - $this->field_name = 'test_list'; - $this->field = array( - 'field_name' => $this->field_name, - 'type' => 'list', - 'cardinality' => 1, - 'settings' => array( - 'allowed_values' => "1|One\n2|Two\n3|Three\n", - ), - ); - $this->field = field_create_field($this->field); - - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_buttons', - ), - ); - $this->instance = field_create_instance($this->instance); - } - - /** - * Test that allowed values can be updated. - */ - function testUpdateAllowedValues() { - $langcode = LANGUAGE_NONE; - - // All three options appear. - $entity = field_test_create_stub_entity(); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); - - // Removed options do not appear. - $this->field['settings']['allowed_values'] = "2|Two"; - field_update_field($this->field); - $entity = field_test_create_stub_entity(); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); - - // Completely new options appear. - $this->field['settings']['allowed_values'] = "10|Update\n20|Twenty"; - field_update_field($this->field); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(empty($form[$this->field_name][$langcode][1]), t('Option 1 does not exist')); - $this->assertTrue(empty($form[$this->field_name][$langcode][2]), t('Option 2 does not exist')); - $this->assertTrue(empty($form[$this->field_name][$langcode][3]), t('Option 3 does not exist')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][10]), t('Option 10 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][20]), t('Option 20 exists')); - - // Options are reset when a new field with the same name is created. - field_delete_field($this->field_name); - unset($this->field['id']); - $this->field['settings']['allowed_values'] = "1|One\n2|Two\n3|Three\n"; - $this->field = field_create_field($this->field); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'options_buttons', - ), - ); - $this->instance = field_create_instance($this->instance); - $entity = field_test_create_stub_entity(); - $form = drupal_get_form('field_test_entity_form', $entity); - $this->assertTrue(!empty($form[$this->field_name][$langcode][1]), t('Option 1 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][2]), t('Option 2 exists')); - $this->assertTrue(!empty($form[$this->field_name][$langcode][3]), t('Option 3 exists')); - } -} - -/** -* List module UI tests. -*/ -class ListFieldUITestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'List field UI', - 'description' => 'Test the List field UI functionality.', - 'group' => 'Field types', - ); - } - - function setUp() { - parent::setUp('field_test', 'field_ui'); - - // Create test user. - $admin_user = $this->drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy')); - $this->drupalLogin($admin_user); - - // Create content type, with underscores. - $type_name = 'test_' . strtolower($this->randomName()); - $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); - $this->type = $type->type; - // Store a valid URL name, with hyphens instead of underscores. - $this->hyphen_type = str_replace('_', '-', $this->type); - - // Create random field name. - $this->field_label = $this->randomString(); - $this->field_name = strtolower($this->randomName()); - } - - /** - * Tests that allowed values are properly validated in the UI. - */ - function testAllowedValues() { - $element_name = "field[settings][allowed_values]"; - - //Test 'List' field type. - $admin_path = $this->createListFieldAndEdit('list'); - //Check that non-integer keys are rejected. - $edit = array($element_name => "1.1|one\n"); - $this->drupalPost($admin_path, $edit, t('Save settings')); - $this->assertText("keys must be integers", t('Form validation failed.')); - - // Test 'List (number)' field type. - $admin_path = $this->createListFieldAndEdit('list_number'); - //Check that non-numeric keys are rejected. - $edit = array($element_name => "1|one\nB|two"); - $this->drupalPost($admin_path, $edit, t('Save settings')); - $this->assertText("each key must be a valid integer or decimal", t('Form validation failed.')); - - //Test 'List (text)' field type. - $admin_path = $this->createListFieldAndEdit('list_text'); - //Check that over long keys are rejected. - $edit = array($element_name => "1|one\n" . $this->randomName(256) . "|two"); - $this->drupalPost($admin_path, $edit, t('Save settings')); - $this->assertText("each key must be a string at most 255 characters long", t('Form validation failed.')); - } - - /** - * Helper function to create list field of a given type and get the edit page. - * - * @param string $type - * 'list', 'list_boolean', 'list_number', or 'list_text' - */ - private function createListFieldAndEdit($type) { - // Create a test field and instance. - $field_name = 'test_' . $type; - $field = array( - 'field_name' => $field_name, - 'type' => $type, - ); - field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'entity_type' => 'node', - 'bundle' => $this->type, - ); - field_create_instance($instance); - - $admin_path = 'admin/structure/types/manage/' . $this->hyphen_type . '/fields/' . $field_name; - return $admin_path; - } - -} - diff --git modules/field/tests/field.test modules/field/tests/field.test deleted file mode 100644 index 36d6284..0000000 --- modules/field/tests/field.test +++ /dev/null @@ -1,2968 +0,0 @@ -default_storage); - } - - /** - * Generate random values for a field_test field. - * - * @param $cardinality - * Number of values to generate. - * @return - * An array of random values, in the format expected for field values. - */ - function _generateTestFieldValues($cardinality) { - $values = array(); - for ($i = 0; $i < $cardinality; $i++) { - // field_test fields treat 0 as 'empty value'. - $values[$i]['value'] = mt_rand(1, 127); - } - return $values; - } - - /** - * Assert that a field has the expected values in an entity. - * - * This function only checks a single column in the field values. - * - * @param $entity - * The entity to test. - * @param $field_name - * The name of the field to test - * @param $langcode - * The language code for the values. - * @param $expected_values - * The array of expected values. - * @param $column - * (Optional) the name of the column to check. - */ - function assertFieldValues($entity, $field_name, $langcode, $expected_values, $column = 'value') { - $e = clone $entity; - field_attach_load('test_entity', array($e->ftid => $e)); - $values = isset($e->{$field_name}[$langcode]) ? $e->{$field_name}[$langcode] : array(); - $this->assertEqual(count($values), count($expected_values), t('Expected number of values were saved.')); - foreach ($expected_values as $key => $value) { - $this->assertEqual($values[$key][$column], $value, t('Value @value was saved correctly.', array('@value' => $value))); - } - } -} - -class FieldAttachTestCase extends FieldTestCase { - function setUp($modules = array()) { - // Since this is a base class for many test cases, support the same - // flexibility that DrupalWebTestCase::setUp() has for the modules to be - // passed in as either an array or a variable number of string arguments. - if (!is_array($modules)) { - $modules = func_get_args(); - } - if (!in_array('field_test', $modules)) { - $modules[] = 'field_test'; - } - parent::setUp($modules); - - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); - $this->field = field_create_field($this->field); - $this->field_id = $this->field['id']; - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->randomName() . '_label', - 'description' => $this->randomName() . '_description', - 'weight' => mt_rand(0, 127), - 'settings' => array( - 'test_instance_setting' => $this->randomName(), - ), - 'widget' => array( - 'type' => 'test_field_widget', - 'label' => 'Test Field', - 'settings' => array( - 'test_widget_setting' => $this->randomName(), - ) - ) - ); - field_create_instance($this->instance); - } -} - -/** - * Unit test class for storage-related field_attach_* functions. - * - * All field_attach_* test work with all field_storage plugins and - * all hook_field_attach_pre_{load,insert,update}() hooks. - */ -class FieldAttachStorageTestCase extends FieldAttachTestCase { - public static function getInfo() { - return array( - 'name' => 'Field attach tests (storage-related)', - 'description' => 'Test storage-related Field Attach API functions.', - 'group' => 'Field API', - ); - } - - /** - * Check field values insert, update and load. - * - * Works independently of the underlying field storage backend. Inserts or - * updates random field data and then loads and verifies the data. - */ - function testFieldAttachSaveLoad() { - // Configure the instance so that we test hook_field_load() (see - // field_test_field_load() in field_test.module). - $this->instance['settings']['test_hook_field_load'] = TRUE; - field_update_instance($this->instance); - $langcode = LANGUAGE_NONE; - - $entity_type = 'test_entity'; - $values = array(); - - // TODO : test empty values filtering and "compression" (store consecutive deltas). - - // Preparation: create three revisions and store them in $revision array. - for ($revision_id = 0; $revision_id < 3; $revision_id++) { - $revision[$revision_id] = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); - // Note: we try to insert one extra value. - $values[$revision_id] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); - $current_revision = $revision_id; - // If this is the first revision do an insert. - if (!$revision_id) { - $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id]; - field_attach_insert($entity_type, $revision[$revision_id]); - } - else { - // Otherwise do an update. - $revision[$revision_id]->{$this->field_name}[$langcode] = $values[$revision_id]; - field_attach_update($entity_type, $revision[$revision_id]); - } - } - - // Confirm current revision loads the correct data. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Current revision: expected number of values')); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'] , $values[$current_revision][$delta]['value'], t('Current revision: expected value %delta was found.', array('%delta' => $delta))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Current revision: extra information for value %delta was found', array('%delta' => $delta))); - } - - // Confirm each revision loads the correct data. - foreach (array_keys($revision) as $revision_id) { - $entity = field_test_create_stub_entity(0, $revision_id, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $entity)); - // Number of values per field loaded equals the field cardinality. - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], t('Revision %revision_id: expected number of values.', array('%revision_id' => $revision_id))); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - // The field value loaded matches the one inserted or updated. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['value'], $values[$revision_id][$delta]['value'], t('Revision %revision_id: expected value %delta was found.', array('%revision_id' => $revision_id, '%delta' => $delta))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$this->field_name}[$langcode][$delta]['additional_key'], 'additional_value', t('Revision %revision_id: extra information for value %delta was found', array('%revision_id' => $revision_id, '%delta' => $delta))); - } - } - } - - /** - * Test the 'multiple' load feature. - */ - function testFieldAttachLoadMultiple() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - - // Define 2 bundles. - $bundles = array( - 1 => 'test_bundle_1', - 2 => 'test_bundle_2', - ); - field_test_create_bundle($bundles[1]); - field_test_create_bundle($bundles[2]); - // Define 3 fields: - // - field_1 is in bundle_1 and bundle_2, - // - field_2 is in bundle_1, - // - field_3 is in bundle_2. - $field_bundles_map = array( - 1 => array(1, 2), - 2 => array(1), - 3 => array(2), - ); - for ($i = 1; $i <= 3; $i++) { - $field_names[$i] = 'field_' . $i; - $field = array('field_name' => $field_names[$i], 'type' => 'test_field'); - $field = field_create_field($field); - $field_ids[$i] = $field['id']; - foreach ($field_bundles_map[$i] as $bundle) { - $instance = array( - 'field_name' => $field_names[$i], - 'entity_type' => 'test_entity', - 'bundle' => $bundles[$bundle], - 'settings' => array( - // Configure the instance so that we test hook_field_load() - // (see field_test_field_load() in field_test.module). - 'test_hook_field_load' => TRUE, - ), - ); - field_create_instance($instance); - } - } - - // Create one test entity per bundle, with random values. - foreach ($bundles as $index => $bundle) { - $entities[$index] = field_test_create_stub_entity($index, $index, $bundle); - $entity = clone($entities[$index]); - $instances = field_info_instances('test_entity', $bundle); - foreach ($instances as $field_name => $instance) { - $values[$index][$field_name] = mt_rand(1, 127); - $entity->$field_name = array($langcode => array(array('value' => $values[$index][$field_name]))); - } - field_attach_insert($entity_type, $entity); - } - - // Check that a single load correctly loads field values for both entities. - field_attach_load($entity_type, $entities); - foreach ($entities as $index => $entity) { - $instances = field_info_instances($entity_type, $bundles[$index]); - foreach ($instances as $field_name => $instance) { - // The field value loaded matches the one inserted. - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], $values[$index][$field_name], t('Entity %index: expected value was found.', array('%index' => $index))); - // The value added in hook_field_load() is found. - $this->assertEqual($entity->{$field_name}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => $index))); - } - } - - // Check that the single-field load option works. - $entity = field_test_create_stub_entity(1, 1, $bundles[1]); - field_attach_load($entity_type, array(1 => $entity), FIELD_LOAD_CURRENT, array('field_id' => $field_ids[1])); - $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['value'], $values[1][$field_names[1]], t('Entity %index: expected value was found.', array('%index' => 1))); - $this->assertEqual($entity->{$field_names[1]}[$langcode][0]['additional_key'], 'additional_value', t('Entity %index: extra information was found', array('%index' => 1))); - $this->assert(!isset($entity->{$field_names[2]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 2, '%field_name' => $field_names[2]))); - $this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3]))); - } - - /** - * Test saving and loading fields using different storage backends. - */ - function testFieldAttachSaveLoadDifferentStorage() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - - // Create two fields using different storage backends, and their instances. - $fields = array( - array( - 'field_name' => 'field_1', - 'type' => 'test_field', - 'cardinality' => 4, - 'storage' => array('type' => 'field_sql_storage') - ), - array( - 'field_name' => 'field_2', - 'type' => 'test_field', - 'cardinality' => 4, - 'storage' => array('type' => 'field_test_storage') - ), - ); - foreach ($fields as $field) { - field_create_field($field); - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - } - - $entity_init = field_test_create_stub_entity(); - - // Create entity and insert random values. - $entity = clone($entity_init); - $values = array(); - foreach ($fields as $field) { - $values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$field['field_name']}[$langcode] = $values[$field['field_name']]; - } - field_attach_insert($entity_type, $entity); - - // Check that values are loaded as expected. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - foreach ($fields as $field) { - $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type']))); - } - } - - /** - * Test storage details alteration. - * - * @see field_test_storage_details_alter() - */ - function testFieldStorageDetailsAlter() { - $field_name = 'field_test_change_my_details'; - $field = array( - 'field_name' => $field_name, - 'type' => 'test_field', - 'cardinality' => 4, - 'storage' => array('type' => 'field_test_storage'), - ); - $field = field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - - $field = field_info_field($instance['field_name']); - $instance = field_info_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']); - - // The storage details are indexed by a storage engine type. - $this->assertTrue(array_key_exists('drupal_variables', $field['storage']['details']), t('The storage type is Drupal variables.')); - - $details = $field['storage']['details']['drupal_variables']; - - // The field_test storage details are indexed by variable name. The details - // are altered, so moon and mars are correct for this test. - $this->assertTrue(array_key_exists('moon', $details[FIELD_LOAD_CURRENT]), t('Moon is available in the instance array.')); - $this->assertTrue(array_key_exists('mars', $details[FIELD_LOAD_REVISION]), t('Mars is available in the instance array.')); - - // Test current and revision storage details together because the columns - // are the same. - foreach ((array) $field['columns'] as $column_name => $attributes) { - $this->assertEqual($details[FIELD_LOAD_CURRENT]['moon'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'moon[FIELD_LOAD_CURRENT]'))); - $this->assertEqual($details[FIELD_LOAD_REVISION]['mars'][$column_name], $column_name, t('Column name %value matches the definition in %bin.', array('%value' => $column_name, '%bin' => 'mars[FIELD_LOAD_REVISION]'))); - } - } - - /** - * Tests insert and update with missing or NULL fields. - */ - function testFieldAttachSaveMissingData() { - $entity_type = 'test_entity'; - $entity_init = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - - // Insert: Field is missing. - $entity = clone($entity_init); - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved')); - - // Insert: Field is NULL. - field_cache_clear(); - $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved')); - - // Add some real data. - field_cache_clear(); - $entity = clone($entity_init); - $values = $this->_generateTestFieldValues(1); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); - - // Update: Field is missing. Data should survive. - field_cache_clear(); - $entity = clone($entity_init); - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Update: missing field leaves existing values in place')); - - // Update: Field is NULL. Data should be wiped. - field_cache_clear(); - $entity = clone($entity_init); - $entity->{$this->field_name} = NULL; - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values')); - - // Re-add some data. - field_cache_clear(); - $entity = clone($entity_init); - $values = $this->_generateTestFieldValues(1); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved')); - - // Update: Field is empty array. Data should be wiped. - field_cache_clear(); - $entity = clone($entity_init); - $entity->{$this->field_name} = array(); - field_attach_update($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values')); - } - - /** - * Test insert with missing or NULL fields, with default value. - */ - function testFieldAttachSaveMissingDataDefaultValue() { - // Add a default value function. - $this->instance['default_value_function'] = 'field_test_default_value'; - field_update_instance($this->instance); - - $entity_type = 'test_entity'; - $entity_init = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - - // Insert: Field is NULL. - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = NULL; - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved')); - - // Insert: Field is missing. - field_cache_clear(); - $entity = clone($entity_init); - field_attach_insert($entity_type, $entity); - - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $values = field_test_default_value($entity_type, $entity, $this->field, $this->instance); - $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Insert: missing field results in default value saved')); - } - - /** - * Test field_attach_delete(). - */ - function testFieldAttachDelete() { - $entity_type = 'test_entity'; - $langcode = LANGUAGE_NONE; - $rev[0] = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Create revision 0 - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $rev[0]->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $rev[0]); - - // Create revision 1 - $rev[1] = field_test_create_stub_entity(0, 1, $this->instance['bundle']); - $rev[1]->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $rev[1]); - - // Create revision 2 - $rev[2] = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - $rev[2]->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $rev[2]); - - // Confirm each revision loads - foreach (array_keys($rev) as $vid) { - $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values."); - } - - // Delete revision 1, confirm the other two still load. - field_attach_delete_revision($entity_type, $rev[1]); - foreach (array(0, 2) as $vid) { - $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity revision $vid has {$this->field['cardinality']} values."); - } - - // Confirm the current revision still loads - $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $read)); - $this->assertEqual(count($read->{$this->field_name}[$langcode]), $this->field['cardinality'], "The test entity current revision has {$this->field['cardinality']} values."); - - // Delete all field data, confirm nothing loads - field_attach_delete($entity_type, $rev[2]); - foreach (array(0, 1, 2) as $vid) { - $read = field_test_create_stub_entity(0, $vid, $this->instance['bundle']); - field_attach_load_revision($entity_type, array(0 => $read)); - $this->assertIdentical($read->{$this->field_name}, array(), "The test entity revision $vid is deleted."); - } - $read = field_test_create_stub_entity(0, 2, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $read)); - $this->assertIdentical($read->{$this->field_name}, array(), t('The test entity current revision is deleted.')); - } - - /** - * Test field_attach_create_bundle() and field_attach_rename_bundle(). - */ - function testFieldAttachCreateRenameBundle() { - // Create a new bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_create_bundle($new_bundle); - - // Add an instance to that bundle. - $this->instance['bundle'] = $new_bundle; - field_create_instance($this->instance); - - // Save an entity with data in the field. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name}[$langcode] = $values; - $entity_type = 'test_entity'; - field_attach_insert($entity_type, $entity); - - // Verify the field data is present on load. - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Data is retrieved for the new bundle"); - - // Rename the bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_rename_bundle($this->instance['bundle'], $new_bundle); - - // Check that the instance definition has been updated. - $this->instance = field_info_instance($entity_type, $this->field_name, $new_bundle); - $this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance."); - - // Verify the field data is present on load. - $entity = field_test_create_stub_entity(0, 0, $new_bundle); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage"); - } - - /** - * Test field_attach_delete_bundle(). - */ - function testFieldAttachDeleteBundle() { - // Create a new bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - $new_bundle = 'test_bundle_' . drupal_strtolower($this->randomName()); - field_test_create_bundle($new_bundle); - - // Add an instance to that bundle. - $this->instance['bundle'] = $new_bundle; - field_create_instance($this->instance); - - // Create a second field for the test bundle - $field_name = drupal_strtolower($this->randomName() . '_field_name'); - $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 1); - field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'entity_type' => 'test_entity', - 'bundle' => $this->instance['bundle'], - 'label' => $this->randomName() . '_label', - 'description' => $this->randomName() . '_description', - 'weight' => mt_rand(0, 127), - // test_field has no instance settings - 'widget' => array( - 'type' => 'test_field_widget', - 'settings' => array( - 'size' => mt_rand(0, 255)))); - field_create_instance($instance); - - // Save an entity with data for both fields - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity->{$this->field_name}[$langcode] = $values; - $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues(1); - field_attach_insert('test_entity', $entity); - - // Verify the fields are present on load - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load('test_entity', array(0 => $entity)); - $this->assertEqual(count($entity->{$this->field_name}[$langcode]), 4, 'First field got loaded'); - $this->assertEqual(count($entity->{$field_name}[$langcode]), 1, 'Second field got loaded'); - - // Delete the bundle. This has to be initiated by the module so that its - // hook_entity_info() is consistent. - field_test_delete_bundle($this->instance['bundle']); - - // Verify no data gets loaded - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - field_attach_load('test_entity', array(0 => $entity)); - $this->assertFalse(isset($entity->{$this->field_name}[$langcode]), 'No data for first field'); - $this->assertFalse(isset($entity->{$field_name}[$langcode]), 'No data for second field'); - - // Verify that the instances are gone - $this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted"); - $this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted"); - } -} - -/** - * Unit test class for non-storage related field_attach_* functions. - */ -class FieldAttachOtherTestCase extends FieldAttachTestCase { - public static function getInfo() { - return array( - 'name' => 'Field attach tests (other)', - 'description' => 'Test other Field Attach API functions.', - 'group' => 'Field API', - ); - } - - /** - * Test field_attach_view() and field_attach_prepare_view(). - */ - function testFieldAttachView() { - $entity_type = 'test_entity'; - $entity_init = field_test_create_stub_entity(); - $langcode = LANGUAGE_NONE; - - // Populate values to be displayed. - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity_init->{$this->field_name}[$langcode] = $values; - - // Simple formatter, label displayed. - $entity = clone($entity_init); - $formatter_setting = $this->randomName(); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'field_test_default', - 'settings' => array( - 'test_formatter_setting' => $formatter_setting, - ) - ), - ); - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - $this->assertRaw($this->instance['label'], "Label is displayed."); - foreach ($values as $delta => $value) { - $this->content = $output; - $this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied."); - } - - // Label hidden. - $entity = clone($entity_init); - $this->instance['display']['full']['label'] = 'hidden'; - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); - - // Field hidden. - $entity = clone($entity_init); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'hidden', - ), - ); - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); - foreach ($values as $delta => $value) { - $this->assertNoRaw($value['value'], "Hidden field: value $delta is not displayed."); - } - - // Multiple formatter. - $entity = clone($entity_init); - $formatter_setting = $this->randomName(); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'field_test_multiple', - 'settings' => array( - 'test_formatter_setting_multiple' => $formatter_setting, - ) - ), - ); - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $display = $formatter_setting; - foreach ($values as $delta => $value) { - $display .= "|$delta:{$value['value']}"; - } - $this->content = $output; - $this->assertRaw($display, "Multiple formatter: all values are displayed, formatter settings are applied."); - - // Test a formatter that uses hook_field_formatter_prepare_view(). - $entity = clone($entity_init); - $formatter_setting = $this->randomName(); - $this->instance['display'] = array( - 'full' => array( - 'label' => 'above', - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $formatter_setting, - ) - ), - ); - field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); - $entity->content = field_attach_view($entity_type, $entity, 'full'); - $output = drupal_render($entity->content); - $this->content = $output; - foreach ($values as $delta => $value) { - $this->content = $output; - $expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1); - $this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied."); - } - - // TODO: - // - check display order with several fields - - // Preprocess template. - $variables = array(); - field_attach_preprocess($entity_type, $entity, $entity->content, $variables); - $result = TRUE; - foreach ($values as $delta => $item) { - if ($variables[$this->field_name][$delta]['value'] !== $item['value']) { - $result = FALSE; - break; - } - } - $this->assertTrue($result, t('Variable $@field_name correctly populated.', array('@field_name' => $this->field_name))); - } - - /** - * Test field cache. - */ - function testFieldAttachCache() { - // Initialize random values and a test entity. - $entity_init = field_test_create_stub_entity(1, 1, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - $values = $this->_generateTestFieldValues($this->field['cardinality']); - - // Non-cacheable entity type. - $entity_type = 'test_entity'; - $cid = "field:$entity_type:{$entity_init->ftid}"; - - // Check that no initial cache entry is present. - $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no initial cache entry')); - - // Save, and check that no cache entry is present. - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on insert')); - - // Load, and check that no cache entry is present. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Non-cached: no cache entry on load')); - - - // Cacheable entity type. - $entity_type = 'test_cacheable_entity'; - $cid = "field:$entity_type:{$entity_init->ftid}"; - $instance = $this->instance; - $instance['entity_type'] = $entity_type; - field_create_instance($instance); - - // Check that no initial cache entry is present. - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no initial cache entry')); - - // Save, and check that no cache entry is present. - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_insert($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on insert')); - - // Load a single field, and check that no cache entry is present. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity), FIELD_LOAD_CURRENT, array('field_id' => $this->field_id)); - $cache = cache_get($cid, 'cache_field'); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on loading a single field')); - - // Load, and check that a cache entry is present with the expected values. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); - - // Update with different values, and check that the cache entry is wiped. - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on update')); - - // Load, and check that a cache entry is present with the expected values. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); - - // Create a new revision, and check that the cache entry is wiped. - $entity_init = field_test_create_stub_entity(1, 2, $this->instance['bundle']); - $values = $this->_generateTestFieldValues($this->field['cardinality']); - $entity = clone($entity_init); - $entity->{$this->field_name}[$langcode] = $values; - field_attach_update($entity_type, $entity); - $cache = cache_get($cid, 'cache_field'); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry on new revision creation')); - - // Load, and check that a cache entry is present with the expected values. - $entity = clone($entity_init); - field_attach_load($entity_type, array($entity->ftid => $entity)); - $cache = cache_get($cid, 'cache_field'); - $this->assertEqual($cache->data[$this->field_name][$langcode], $values, t('Cached: correct cache entry on load')); - - // Delete, and check that the cache entry is wiped. - field_attach_delete($entity_type, $entity); - $this->assertFalse(cache_get($cid, 'cache_field'), t('Cached: no cache entry after delete')); - } - - /** - * Test field_attach_validate(). - * - * Verify that field_attach_validate() invokes the correct - * hook_field_validate. - */ - function testFieldAttachValidate() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - $langcode = LANGUAGE_NONE; - - // Set up values to generate errors - $values = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = -1; - } - // Arrange for item 1 not to generate an error - $values[1]['value'] = 1; - $entity->{$this->field_name}[$langcode] = $values; - - try { - field_attach_validate($entity_type, $entity); - } - catch (FieldValidationException $e) { - $errors = $e->errors; - } - - foreach ($values as $delta => $value) { - if ($value['value'] != 1) { - $this->assertIdentical($errors[$this->field_name][$langcode][$delta][0]['error'], 'field_test_invalid', "Error set on value $delta"); - $this->assertEqual(count($errors[$this->field_name][$langcode][$delta]), 1, "Only one error set on value $delta"); - unset($errors[$this->field_name][$langcode][$delta]); - } - else { - $this->assertFalse(isset($errors[$this->field_name][$langcode][$delta]), "No error set on value $delta"); - } - } - $this->assertEqual(count($errors[$this->field_name][$langcode]), 0, 'No extraneous errors set'); - - // Check that cardinality is validated. - $entity->{$this->field_name}[$langcode] = $this->_generateTestFieldValues($this->field['cardinality'] + 1); - try { - field_attach_validate($entity_type, $entity); - } - catch (FieldValidationException $e) { - $errors = $e->errors; - } - $this->assertEqual($errors[$this->field_name][$langcode][0][0]['error'], 'field_cardinality', t('Cardinality validation failed.')); - - } - - /** - * Test field_attach_form(). - * - * This could be much more thorough, but it does verify that the correct - * widgets show up. - */ - function testFieldAttachForm() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - $form = array(); - $form_state = form_state_defaults(); - field_attach_form($entity_type, $entity, $form, $form_state); - - $langcode = LANGUAGE_NONE; - $this->assertEqual($form[$this->field_name][$langcode]['#title'], $this->instance['label'], "Form title is {$this->instance['label']}"); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - // field_test_widget uses 'textfield' - $this->assertEqual($form[$this->field_name][$langcode][$delta]['value']['#type'], 'textfield', "Form delta $delta widget is textfield"); - } - } - - /** - * Test field_attach_submit(). - */ - function testFieldAttachSubmit() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Build the form. - $form = array(); - $form_state = form_state_defaults(); - field_attach_form($entity_type, $entity, $form, $form_state); - - // Simulate incoming values. - $values = array(); - $weights = array(); - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$delta]['value'] = mt_rand(1, 127); - // Assign random weight. - do { - $weight = mt_rand(0, $this->field['cardinality']); - } while (in_array($weight, $weights)); - $weights[$delta] = $weight; - $values[$delta]['_weight'] = $weight; - } - // Leave an empty value. 'field_test' fields are empty if empty(). - $values[1]['value'] = 0; - - $langcode = LANGUAGE_NONE; - // Pretend the form has been built. - drupal_prepare_form('field_test_entity_form', $form, $form_state); - drupal_process_form('field_test_entity_form', $form, $form_state); - $form_state['values'][$this->field_name][$langcode] = $values; - field_attach_submit($entity_type, $entity, $form, $form_state); - - asort($weights); - $expected_values = array(); - foreach ($weights as $key => $value) { - if ($key != 1) { - $expected_values[] = array('value' => $values[$key]['value']); - } - } - $this->assertIdentical($entity->{$this->field_name}[$langcode], $expected_values, 'Submit filters empty values'); - } -} - -class FieldInfoTestCase extends FieldTestCase { - - public static function getInfo() { - return array( - 'name' => 'Field info tests', - 'description' => 'Get information about existing fields, instances and bundles.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - } - - /** - * Test that field types and field definitions are correcly cached. - */ - function testFieldInfo() { - // Test that field_test module's fields, widgets, and formatters show up. - $field_test_info = field_test_field_info(); - $formatter_info = field_test_field_formatter_info(); - $widget_info = field_test_field_widget_info(); - $storage_info = field_test_field_storage_info(); - - $info = field_info_field_types(); - foreach ($field_test_info as $t_key => $field_type) { - foreach ($field_type as $key => $val) { - $this->assertEqual($info[$t_key][$key], $val, t("Field type $t_key key $key is $val")); - } - $this->assertEqual($info[$t_key]['module'], 'field_test', t("Field type field_test module appears")); - } - - $info = field_info_formatter_types(); - foreach ($formatter_info as $f_key => $formatter) { - foreach ($formatter as $key => $val) { - $this->assertEqual($info[$f_key][$key], $val, t("Formatter type $f_key key $key is $val")); - } - $this->assertEqual($info[$f_key]['module'], 'field_test', t("Formatter type field_test module appears")); - } - - $info = field_info_widget_types(); - foreach ($widget_info as $w_key => $widget) { - foreach ($widget as $key => $val) { - $this->assertEqual($info[$w_key][$key], $val, t("Widget type $w_key key $key is $val")); - } - $this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears")); - } - - $info = field_info_storage_types(); - foreach ($storage_info as $s_key => $storage) { - foreach ($storage as $key => $val) { - $this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val")); - } - $this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears")); - } - - // Verify that no unexpected instances exist. - $core_fields = field_info_fields(); - $instances = field_info_instances('test_entity', 'test_bundle'); - $this->assertTrue(empty($instances), t('With no instances, info bundles is empty.')); - - // Create a field, verify it shows up. - $field = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'test_field', - ); - field_create_field($field); - $fields = field_info_fields(); - $this->assertEqual(count($fields), count($core_fields) + 1, t('One new field exists')); - $this->assertEqual($fields[$field['field_name']]['field_name'], $field['field_name'], t('info fields contains field name')); - $this->assertEqual($fields[$field['field_name']]['type'], $field['type'], t('info fields contains field type')); - $this->assertEqual($fields[$field['field_name']]['module'], 'field_test', t('info fields contains field module')); - $settings = array('test_field_setting' => 'dummy test string'); - foreach ($settings as $key => $val) { - $this->assertEqual($fields[$field['field_name']]['settings'][$key], $val, t("Field setting $key has correct default value $val")); - } - $this->assertEqual($fields[$field['field_name']]['cardinality'], 1, t('info fields contains cardinality 1')); - $this->assertEqual($fields[$field['field_name']]['active'], 1, t('info fields contains active 1')); - - // Create an instance, verify that it shows up - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->randomName(), - 'description' => $this->randomName(), - 'weight' => mt_rand(0, 127), - // test_field has no instance settings - 'widget' => array( - 'type' => 'test_field_widget', - 'settings' => array( - 'test_setting' => 999))); - field_create_instance($instance); - - $instances = field_info_instances('test_entity', $instance['bundle']); - $this->assertEqual(count($instances), 1, t('One instance shows up in info when attached to a bundle.')); - $this->assertTrue($instance < $instances[$instance['field_name']], t('Instance appears in info correctly')); - } - - /** - * Test that cached field definitions are ready for current runtime context. - */ - function testFieldPrepare() { - $field_definition = array( - 'field_name' => 'field', - 'type' => 'test_field', - ); - field_create_field($field_definition); - - // Simulate a stored field definition missing a field setting (e.g. a - // third-party module adding a new field setting has been enabled, and - // existing fields do not know the setting yet). - $data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField(); - $data = unserialize($data); - $data['settings'] = array(); - db_update('field_config') - ->fields(array('data' => serialize($data))) - ->condition('field_name', $field_definition['field_name']) - ->execute(); - - field_cache_clear(); - - // Read the field back. - $field = field_info_field($field_definition['field_name']); - - // Check that all expected settings are in place. - $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($field['settings'], $field_type['settings'], t('All expected default field settings are present.')); - } - - /** - * Test that cached instance definitions are ready for current runtime context. - */ - function testInstancePrepare() { - $field_definition = array( - 'field_name' => 'field', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $instance_definition = array( - 'field_name' => $field_definition['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - field_create_instance($instance_definition); - - // Simulate a stored instance definition missing various settings (e.g. a - // third-party module adding instance, widget or display settings has been - // enabled, but existing instances do not know the new settings). - $data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField(); - $data = unserialize($data); - $data['settings'] = array(); - $data['widget']['settings'] = 'unavailable_widget'; - $data['widget']['settings'] = array(); - $data['display']['default']['type'] = 'unavailable_formatter'; - $data['display']['default']['settings'] = array(); - db_update('field_config_instance') - ->fields(array('data' => serialize($data))) - ->condition('field_name', $instance_definition['field_name']) - ->condition('bundle', $instance_definition['bundle']) - ->execute(); - - field_cache_clear(); - - // Read the instance back. - $instance = field_info_instance($instance_definition['entity_type'], $instance_definition['field_name'], $instance_definition['bundle']); - - // Check that all expected instance settings are in place. - $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($instance['settings'], $field_type['instance_settings'] , t('All expected instance settings are present.')); - - // Check that the default widget is used and expected settings are in place. - $this->assertIdentical($instance['widget']['type'], $field_type['default_widget'], t('Unavailable widget replaced with default widget.')); - $widget_type = field_info_widget_types($instance['widget']['type']); - $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.')); - - // Check that display settings are set for the 'default' mode. - $display = $instance['display']['default']; - $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode")); - $formatter_type = field_info_formatter_types($display['type']); - $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode")); - } - - /** - * Test that instances on disabled entity types are filtered out. - */ - function testInstanceDisabledEntityType() { - // For this test the field type and the entity type must be exposed by - // different modules. - $field_definition = array( - 'field_name' => 'field', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $instance_definition = array( - 'field_name' => 'field', - 'entity_type' => 'comment', - 'bundle' => 'comment_node_article', - ); - field_create_instance($instance_definition); - - // Disable coment module. This clears field_info cache. - module_disable(array('comment')); - $this->assertNull(field_info_instance('comment', 'field', 'comment_node_article'), t('No instances are returned on disabled entity types.')); - } - - /** - * Test that the field_info settings convenience functions work. - */ - function testSettingsInfo() { - $info = field_test_field_info(); - foreach ($info as $type => $data) { - $this->assertIdentical(field_info_field_settings($type), $data['settings'], "field_info_field_settings returns {$type}'s field settings"); - $this->assertIdentical(field_info_instance_settings($type), $data['instance_settings'], "field_info_field_settings returns {$type}'s field instance settings"); - } - - $info = field_test_field_widget_info(); - foreach ($info as $type => $data) { - $this->assertIdentical(field_info_widget_settings($type), $data['settings'], "field_info_widget_settings returns {$type}'s widget settings"); - } - - $info = field_test_field_formatter_info(); - foreach ($info as $type => $data) { - $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); - } - } -} - -class FieldFormTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field form tests', - 'description' => 'Test Field form handling.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - - $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($web_user); - - $this->field_single = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field'); - $this->field_multiple = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field', 'cardinality' => 4); - $this->field_unlimited = array('field_name' => drupal_strtolower($this->randomName()), 'type' => 'test_field', 'cardinality' => FIELD_CARDINALITY_UNLIMITED); - - $this->instance = array( - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->randomName() . '_label', - 'description' => $this->randomName() . '_description', - 'weight' => mt_rand(0, 127), - 'settings' => array( - 'test_instance_setting' => $this->randomName(), - ), - 'widget' => array( - 'type' => 'test_field_widget', - 'label' => 'Test Field', - 'settings' => array( - 'test_widget_setting' => $this->randomName(), - ) - ) - ); - } - - function testFieldFormSingle() { - $this->field = $this->field_single; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget is displayed'); - $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); - // TODO : check that the widget is populated with default value ? - - // Submit with invalid value (field-level validation). - $edit = array("{$this->field_name}[$langcode][0][value]" => -1); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $this->instance['label'])), 'Field validation fails with invalid input.'); - // TODO : check that the correct field is flagged for error. - - // Create an entity - $value = mt_rand(1, 127); - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); - - // Display edit form. - $this->drupalGet('test-entity/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", $value, 'Widget is displayed with the correct default value'); - $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); - - // Update the entity. - $value = mt_rand(1, 127); - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was updated'); - - // Empty the field. - $value = ''; - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); - $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), 'Entity was updated'); - $entity = field_test_entity_test_load($id); - $this->assertIdentical($entity->{$this->field_name}, array(), 'Field was emptied'); - - } - - function testFieldFormSingleRequired() { - $this->field = $this->field_single; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - $this->instance['required'] = TRUE; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Submit with missing required value. - $edit = array(); - $this->drupalPost('test-entity/add/test-bundle', $edit, t('Save')); - $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation'); - - // Create an entity - $value = mt_rand(1, 127); - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$this->field_name}[$langcode][0]['value'], $value, 'Field value was saved'); - - // Edit with missing required value. - $value = ''; - $edit = array("{$this->field_name}[$langcode][0][value]" => $value); - $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); - $this->assertRaw(t('!name field is required.', array('!name' => $this->instance['label'])), 'Required field with no value fails validation'); - } - -// function testFieldFormMultiple() { -// $this->field = $this->field_multiple; -// $this->field_name = $this->field['field_name']; -// $this->instance['field_name'] = $this->field_name; -// field_create_field($this->field); -// field_create_instance($this->instance); -// } - - function testFieldFormUnlimited() { - $this->field = $this->field_unlimited; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form -> 1 widget. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed'); - $this->assertNoField("{$this->field_name}[$langcode][1][value]", 'No extraneous widget is displayed'); - - // Press 'add more' button -> 2 widgets. - $this->drupalPost(NULL, array(), t('Add another item')); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', 'Widget 1 is displayed'); - $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed'); - $this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed'); - // TODO : check that non-field inpurs are preserved ('title')... - - // Yet another time so that we can play with more values -> 3 widgets. - $this->drupalPost(NULL, array(), t('Add another item')); - - // Prepare values and weights. - $count = 3; - $delta_range = $count - 1; - $values = $weights = $pattern = $expected_values = $edit = array(); - for ($delta = 0; $delta <= $delta_range; $delta++) { - // Assign unique random weights. - do { - $weight = mt_rand(-$delta_range, $delta_range); - } while (in_array($weight, $weights)); - $value = mt_rand(1, 127); - $edit["$this->field_name[$langcode][$delta][value]"] = $value; - $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight; - // We'll need three slightly different formats to check the values. - $values[$delta] = $value; - $weights[$delta] = $weight; - $field_values[$weight]['value'] = (string) $value; - $pattern[$weight] = "]*value=\"$value\" [^>]*"; - } - - // Press 'add more' button -> 4 widgets - $this->drupalPost(NULL, $edit, t('Add another item')); - for ($delta = 0; $delta <= $delta_range; $delta++) { - $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $weights[$delta], "Widget $delta has the right weight"); - } - ksort($pattern); - $pattern = implode('.*', array_values($pattern)); - $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order'); - $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", '', "New widget is displayed"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight"); - $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); - - // Submit the form and create the entity. - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created'); - $entity = field_test_entity_test_load($id); - ksort($field_values); - $field_values = array_values($field_values); - $this->assertIdentical($entity->{$this->field_name}[$langcode], $field_values, 'Field values were saved in the correct order'); - - // Display edit form: check that the expected number of widgets is - // displayed, with correct values change values, reorder, leave an empty - // value in the middle. - // Submit: check that the entity is updated with correct values - // Re-submit: check that the field can be emptied. - - // Test with several multiple fields in a form - } - - function testFieldFormJSAddMore() { - $this->field = $this->field_unlimited; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form -> 1 widget. - $this->drupalGet('test-entity/add/test-bundle'); - - // Press 'add more' button a couple times -> 3 widgets. - // drupalPostAJAX() will not work iteratively, so we add those through - // non-JS submission. - $this->drupalPost(NULL, array(), t('Add another item')); - $this->drupalPost(NULL, array(), t('Add another item')); - - // Prepare values and weights. - $count = 3; - $delta_range = $count - 1; - $values = $weights = $pattern = $expected_values = $edit = array(); - for ($delta = 0; $delta <= $delta_range; $delta++) { - // Assign unique random weights. - do { - $weight = mt_rand(-$delta_range, $delta_range); - } while (in_array($weight, $weights)); - $value = mt_rand(1, 127); - $edit["$this->field_name[$langcode][$delta][value]"] = $value; - $edit["$this->field_name[$langcode][$delta][_weight]"] = $weight; - // We'll need three slightly different formats to check the values. - $values[$delta] = $value; - $weights[$delta] = $weight; - $field_values[$weight]['value'] = (string) $value; - $pattern[$weight] = "]*value=\"$value\" [^>]*"; - } - // Press 'add more' button through AJAX, and place the expected HTML result - // as the tested content. - $commands = $this->drupalPostAJAX(NULL, $edit, $this->field_name . '_add_more'); - $this->content = $commands[1]['data']; - - for ($delta = 0; $delta <= $delta_range; $delta++) { - $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", $values[$delta], "Widget $delta is displayed and has the right value"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $weights[$delta], "Widget $delta has the right weight"); - } - ksort($pattern); - $pattern = implode('.*', array_values($pattern)); - $this->assertPattern("|$pattern|s", 'Widgets are displayed in the correct order'); - $this->assertFieldByName("$this->field_name[$langcode][$delta][value]", '', "New widget is displayed"); - $this->assertFieldByName("$this->field_name[$langcode][$delta][_weight]", $delta, "New widget has the right weight"); - $this->assertNoField("$this->field_name[$langcode][" . ($delta + 1) . '][value]', 'No extraneous widget is displayed'); - } - - /** - * Tests widgets handling multiple values. - */ - function testFieldFormMultipleWidget() { - // Create a field with fixed cardinality and an instance using a multiple - // widget. - $this->field = $this->field_multiple; - $this->field_name = $this->field['field_name']; - $this->instance['field_name'] = $this->field_name; - $this->instance['widget']['type'] = 'test_field_widget_multiple'; - field_create_field($this->field); - field_create_instance($this->instance); - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed.')); - - // Create entity with three values. - $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3'); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - - // Check that the values were saved. - $entity_init = field_test_create_stub_entity($id); - $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); - - // Display the form, check that the values are correctly filled in. - $this->drupalGet('test-entity/' . $id . '/edit'); - $this->assertFieldByName("{$this->field_name}[$langcode]", '1, 2, 3', t('Widget is displayed.')); - - // Submit the form with more values than the field accepts. - $edit = array("{$this->field_name}[$langcode]" => '1, 2, 3, 4, 5'); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw('this field cannot hold more than 4 values', t('Form validation failed.')); - // Check that the field values were not submitted. - $this->assertFieldValues($entity_init, $this->field_name, $langcode, array(1, 2, 3)); - } - - /** - * Tests fields with no 'edit' access. - */ - function testFieldFormAccess() { - // Create a "regular" field. - $field = $this->field_single; - $field_name = $field['field_name']; - $instance = $this->instance; - $instance['field_name'] = $field_name; - field_create_field($field); - field_create_instance($instance); - - // Create a field with no edit access - see field_test_field_access(). - $field_no_access = array( - 'field_name' => 'field_no_edit_access', - 'type' => 'test_field', - ); - $field_name_no_access = $field_no_access['field_name']; - $instance_no_access = array( - 'field_name' => $field_name_no_access, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'default_value' => array(0 => array('value' => 99)), - ); - field_create_field($field_no_access); - field_create_instance($instance_no_access); - - $langcode = LANGUAGE_NONE; - - // Display creation form. - $this->drupalGet('test-entity/add/test-bundle'); - $this->assertNoFieldByName("{$field_name_no_access}[$langcode][0][value]", '', t('Widget is not displayed if field access is denied.')); - - // Create entity. - $edit = array("{$field_name}[$langcode][0][value]" => 1); - $this->drupalPost(NULL, $edit, t('Save')); - preg_match('|test-entity/(\d+)/edit|', $this->url, $match); - $id = $match[1]; - - // Check that the default value was saved. - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('Default value was saved for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 1, t('Entered value vas saved for the field with edit access.')); - - // Create a new revision. - $edit = array("{$field_name}[$langcode][0][value]" => 2, 'revision' => TRUE); - $this->drupalPost('test-entity/' . $id . '/edit', $edit, t('Save')); - - // Check that the new revision has the expected values. - $entity = field_test_entity_test_load($id); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); - - // Check that the revision is also saved in the revisions table. - $entity = field_test_entity_test_load($id, $entity->ftvid); - $this->assertEqual($entity->{$field_name_no_access}[$langcode][0]['value'], 99, t('New revision has the expected value for the field with no edit access.')); - $this->assertEqual($entity->{$field_name}[$langcode][0]['value'], 2, t('New revision has the expected value for the field with edit access.')); - } -} - -class FieldDisplayAPITestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field Display API tests', - 'description' => 'Test the display API.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - - // Create a field and instance. - $this->field_name = 'test_field'; - $this->label = $this->randomName(); - $this->cardinality = 4; - - $this->field = array( - 'field_name' => $this->field_name, - 'type' => 'test_field', - 'cardinality' => $this->cardinality, - ); - $this->instance = array( - 'field_name' => $this->field_name, - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'label' => $this->label, - 'display' => array( - 'default' => array( - 'type' => 'field_test_default', - 'settings' => array( - 'test_formatter_setting' => $this->randomName(), - ), - ), - 'teaser' => array( - 'type' => 'field_test_default', - 'settings' => array( - 'test_formatter_setting' => $this->randomName(), - ), - ), - ), - ); - field_create_field($this->field); - field_create_instance($this->instance); - - // Create an entity with values. - $this->values = $this->_generateTestFieldValues($this->cardinality); - $this->entity = field_test_create_stub_entity(); - $this->is_new = TRUE; - $this->entity->{$this->field_name}[LANGUAGE_NONE] = $this->values; - field_test_entity_save($this->entity); - } - - /** - * Test the field_view_field() function. - */ - function testFieldViewField() { - // No display settings: check that default display settings are used. - $output = field_view_field('test_entity', $this->entity, $this->field_name); - $this->drupalSetContent(drupal_render($output)); - $settings = field_info_formatter_settings('field_test_default'); - $setting = $settings['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); - foreach($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Check that explicit display settings are used. - $display = array( - 'label' => 'hidden', - 'type' => 'field_test_multiple', - 'settings' => array( - 'test_formatter_setting_multiple' => $this->randomName(), - ), - ); - $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); - $this->drupalSetContent(drupal_render($output)); - $setting = $display['settings']['test_formatter_setting_multiple']; - $this->assertNoText($this->label, t('Label was not displayed.')); - $array = array(); - foreach($this->values as $delta => $value) { - $array[] = $delta . ':' . $value['value']; - } - $this->assertText($setting . '|' . implode('|', $array), t('Values were displayed with expected setting.')); - - // Check the prepare_view steps are invoked. - $display = array( - 'label' => 'hidden', - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $this->randomName(), - ), - ); - $output = field_view_field('test_entity', $this->entity, $this->field_name, $display); - $view = drupal_render($output); - $this->drupalSetContent($view); - $setting = $display['settings']['test_formatter_setting_additional']; - $this->assertNoText($this->label, t('Label was not displayed.')); - foreach ($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // View mode: check that display settings specified in the instance are - // used. - $output = field_view_field('test_entity', $this->entity, $this->field_name, 'teaser'); - $this->drupalSetContent(drupal_render($output)); - $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); - foreach($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Unknown view mode: check that display settings for 'default' view mode - // are used. - $output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_view_mode'); - $this->drupalSetContent(drupal_render($output)); - $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; - $this->assertText($this->label, t('Label was displayed.')); - foreach($this->values as $delta => $value) { - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - } - - /** - * Test the field_view_value() function. - */ - function testFieldViewValue() { - // No display settings: check that default display settings are used. - $settings = field_info_formatter_settings('field_test_default'); - $setting = $settings['test_formatter_setting']; - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Check that explicit display settings are used. - $display = array( - 'type' => 'field_test_multiple', - 'settings' => array( - 'test_formatter_setting_multiple' => $this->randomName(), - ), - ); - $setting = $display['settings']['test_formatter_setting_multiple']; - $array = array(); - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|0:' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Check that prepare_view steps are invoked. - $display = array( - 'type' => 'field_test_with_prepare_view', - 'settings' => array( - 'test_formatter_setting_additional' => $this->randomName(), - ), - ); - $setting = $display['settings']['test_formatter_setting_additional']; - $array = array(); - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, $display); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // View mode: check that display settings specified in the instance are - // used. - $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'teaser'); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - - // Unknown view mode: check that display settings for 'default' view mode - // are used. - $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; - foreach ($this->values as $delta => $value) { - $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'unknown_view_mode'); - $this->drupalSetContent(drupal_render($output)); - $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); - } - } -} - -class FieldCrudTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field CRUD tests', - 'description' => 'Test field create, read, update, and delete.', - 'group' => 'Field API', - ); - } - - function setUp() { - // field_update_field() tests use number.module - parent::setUp('field_test', 'number'); - } - - // TODO : test creation with - // - a full fledged $field structure, check that all the values are there - // - a minimal $field structure, check all default values are set - // defer actual $field comparison to a helper function, used for the two cases above - - /** - * Test the creation of a field. - */ - function testCreateField() { - $field_definition = array( - 'field_name' => 'field_2', - 'type' => 'test_field', - ); - field_test_memorize(); - $field_definition = field_create_field($field_definition); - $mem = field_test_memorize(); - $this->assertIdentical($mem['field_test_field_create_field'][0][0], $field_definition, 'hook_field_create_field() called with correct arguments.'); - - // Read the raw record from the {field_config_instance} table. - $result = db_query('SELECT * FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name'])); - $record = $result->fetchAssoc(); - $record['data'] = unserialize($record['data']); - - // Ensure that basic properties are preserved. - $this->assertEqual($record['field_name'], $field_definition['field_name'], t('The field name is properly saved.')); - $this->assertEqual($record['type'], $field_definition['type'], t('The field type is properly saved.')); - - // Ensure that cardinality defaults to 1. - $this->assertEqual($record['cardinality'], 1, t('Cardinality defaults to 1.')); - - // Ensure that default settings are present. - $field_type = field_info_field_types($field_definition['type']); - $this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.')); - - // Ensure that default storage was set. - $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.')); - - // Guarantee that the name is unique. - try { - field_create_field($field_definition); - $this->fail(t('Cannot create two fields with the same name.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create two fields with the same name.')); - } - - // Check that field type is required. - try { - $field_definition = array( - 'field_name' => 'field_1', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with no type.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with no type.')); - } - - // Check that field name is required. - try { - $field_definition = array( - 'type' => 'test_field' - ); - field_create_field($field_definition); - $this->fail(t('Cannot create an unnamed field.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create an unnamed field.')); - } - - // Check that field name must start with a letter or _. - try { - $field_definition = array( - 'field_name' => '2field_2', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with a name starting with a digit.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with a name starting with a digit.')); - } - - // Check that field name must only contain lowercase alphanumeric or _. - try { - $field_definition = array( - 'field_name' => 'field#_3', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with a name containing an illegal character.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with a name containing an illegal character.')); - } - - // Check that field name cannot be longer than 32 characters long. - try { - $field_definition = array( - 'field_name' => '_12345678901234567890123456789012', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $this->fail(t('Cannot create a field with a name longer than 32 characters.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field with a name longer than 32 characters.')); - } - - // Check that field name can not be an entity key. - // "ftvid" is known as an entity key from the "test_entity" type. - try { - $field_definition = array( - 'type' => 'test_field', - 'field_name' => 'ftvid', - ); - $field = field_create_field($field_definition); - $this->fail(t('Cannot create a field bearing the name of an entity key.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create a field bearing the name of an entity key.')); - } - } - - /** - * Test failure to create a field. - */ - function testCreateFieldFail() { - $field_name = 'duplicate'; - $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure')); - $query = db_select('field_config')->condition('field_name', $field_name)->countQuery(); - - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); - - // Try to create the field. - try { - $field = field_create_field($field_definition); - $this->assertTrue(FALSE, 'Field creation (correctly) fails.'); - } - catch (Exception $e) { - $this->assertTrue(TRUE, 'Field creation (correctly) fails.'); - } - - // The field does not appear in field_config. - $count = $query->execute()->fetchField(); - $this->assertEqual($count, 0, 'A field_config row for the field does not exist.'); - } - - /** - * Test reading back a field definition. - */ - function testReadField() { - $field_definition = array( - 'field_name' => 'field_1', - 'type' => 'test_field', - ); - field_create_field($field_definition); - - // Read the field back. - $field = field_read_field($field_definition['field_name']); - $this->assertTrue($field_definition < $field, t('The field was properly read.')); - } - - /** - * Test creation of indexes on data column. - */ - function testFieldIndexes() { - // Check that indexes specified by the field type are used by default. - $field_definition = array( - 'field_name' => 'field_1', - 'type' => 'test_field', - ); - field_create_field($field_definition); - $field = field_read_field($field_definition['field_name']); - $expected_indexes = array('value' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field type indexes saved by default')); - - // Check that indexes specified by the field definition override the field - // type indexes. - $field_definition = array( - 'field_name' => 'field_2', - 'type' => 'test_field', - 'indexes' => array( - 'value' => array(), - ), - ); - field_create_field($field_definition); - $field = field_read_field($field_definition['field_name']); - $expected_indexes = array('value' => array()); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes override field type indexes')); - - // Check that indexes specified by the field definition add to the field - // type indexes. - $field_definition = array( - 'field_name' => 'field_3', - 'type' => 'test_field', - 'indexes' => array( - 'value_2' => array('value'), - ), - ); - field_create_field($field_definition); - $field = field_read_field($field_definition['field_name']); - $expected_indexes = array('value' => array('value'), 'value_2' => array('value')); - $this->assertEqual($field['indexes'], $expected_indexes, t('Field definition indexes are merged with field type indexes')); - } - - /** - * Test the deletion of a field. - */ - function testDeleteField() { - // TODO: Also test deletion of the data stored in the field ? - - // Create two fields (so we can test that only one is deleted). - $this->field = array('field_name' => 'field_1', 'type' => 'test_field'); - field_create_field($this->field); - $this->another_field = array('field_name' => 'field_2', 'type' => 'test_field'); - field_create_field($this->another_field); - - // Create instances for each. - $this->instance_definition = array( - 'field_name' => $this->field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - 'widget' => array( - 'type' => 'test_field_widget', - ), - ); - field_create_instance($this->instance_definition); - $this->another_instance_definition = $this->instance_definition; - $this->another_instance_definition['field_name'] = $this->another_field['field_name']; - field_create_instance($this->another_instance_definition); - - // Test that the first field is not deleted, and then delete it. - $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field is not marked for deletion.')); - field_delete_field($this->field['field_name']); - - // Make sure that the field is marked as deleted when it is specifically - // loaded. - $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.')); - - // Make sure that this field's instance is marked as deleted when it is - // specifically loaded. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($instance['deleted']), t('An instance for a deleted field is marked for deletion.')); - - // Try to load the field normally and make sure it does not show up. - $field = field_read_field($this->field['field_name']); - $this->assertTrue(empty($field), t('A deleted field is not loaded by default.')); - - // Try to load the instance normally and make sure it does not show up. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(empty($instance), t('An instance for a deleted field is not loaded by default.')); - - // Make sure the other field (and its field instance) are not deleted. - $another_field = field_read_field($this->another_field['field_name']); - $this->assertTrue(!empty($another_field) && empty($another_field['deleted']), t('A non-deleted field is not marked for deletion.')); - $another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); - $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('An instance of a non-deleted field is not marked for deletion.')); - - // Try to create a new field the same name as a deleted field and - // write data into it. - field_create_field($this->field); - field_create_instance($this->instance_definition); - $field = field_read_field($this->field['field_name']); - $this->assertTrue(!empty($field) && empty($field['deleted']), t('A new field with a previously used name is created.')); - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new instance for a previously used field name is created.')); - - // Save an entity with data for the field - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - $langcode = LANGUAGE_NONE; - $values[0]['value'] = mt_rand(1, 127); - $entity->{$field['field_name']}[$langcode] = $values; - $entity_type = 'test_entity'; - field_attach_insert('test_entity', $entity); - - // Verify the field is present on load - $entity = field_test_create_stub_entity(0, 0, $this->instance_definition['bundle']); - field_attach_load($entity_type, array(0 => $entity)); - $this->assertIdentical(count($entity->{$field['field_name']}[$langcode]), count($values), "Data in previously deleted field saves and loads correctly"); - foreach ($values as $delta => $value) { - $this->assertEqual($entity->{$field['field_name']}[$langcode][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly"); - } - } - - function testUpdateNonExistentField() { - $test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal'); - try { - field_update_field($test_field); - $this->fail(t('Cannot update a field that does not exist.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update a field that does not exist.')); - } - } - - function testUpdateFieldType() { - $field = array('field_name' => 'field_type', 'type' => 'number_decimal'); - $field = field_create_field($field); - - $test_field = array('field_name' => 'field_type', 'type' => 'number_integer'); - try { - field_update_field($test_field); - $this->fail(t('Cannot update a field to a different type.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot update a field to a different type.')); - } - } - - /** - * Test updating a field. - */ - function testUpdateField() { - // Create a decimal 5.2 field. - $field = array('field_name' => 'decimal53', 'type' => 'number_decimal', 'cardinality' => 3, 'settings' => array('precision' => 5, 'scale' => 2)); - $field = field_create_field($field); - $instance = array('field_name' => 'decimal53', 'entity_type' => 'test_entity', 'bundle' => 'test_bundle'); - $instance = field_create_instance($instance); - - // Update it to a deciaml 5.3 field. - $field['settings']['scale'] = 3; - field_update_field($field); - - // Save values with 2, 3, and 4 decimal places. - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - $entity->decimal53[LANGUAGE_NONE][0]['value'] = '1.23'; - $entity->decimal53[LANGUAGE_NONE][1]['value'] = '1.235'; - $entity->decimal53[LANGUAGE_NONE][2]['value'] = '1.2355'; - field_attach_insert('test_entity', $entity); - $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); - - // Verify that the updated 5.3 field rounds to 3 decimal places. - field_attach_load('test_entity', array(0 => $entity)); - $this->assertEqual($entity->decimal53[LANGUAGE_NONE][0]['value'], '1.23', t('2 decimal places are left alone')); - $this->assertEqual($entity->decimal53[LANGUAGE_NONE][1]['value'], '1.235', t('3 decimal places are left alone')); - $this->assertEqual($entity->decimal53[LANGUAGE_NONE][2]['value'], '1.236', t('4 decimal places are rounded to 3')); - } - - /** - * Test field type modules forbidding an update. - */ - function testUpdateFieldForbid() { - $field = array('field_name' => 'forbidden', 'type' => 'test_field', 'settings' => array('changeable' => 0, 'unchangeable' => 0)); - $field = field_create_field($field); - $field['settings']['changeable']++; - try { - field_update_field($field); - $this->pass(t("A changeable setting can be updated.")); - } - catch (FieldException $e) { - $this->fail(t("An unchangeable setting cannot be updated.")); - } - $field['settings']['unchangeable']++; - try { - field_update_field($field); - $this->fail(t("An unchangeable setting can be updated.")); - } - catch (FieldException $e) { - $this->pass(t("An unchangeable setting cannot be updated.")); - } - } - - /** - * Test that fields are properly marked active or inactive. - */ - function testActive() { - $field_definition = array( - 'field_name' => 'field_1', - 'type' => 'test_field', - // For this test, we need a storage backend provided by a different - // module than field_test.module. - 'storage' => array( - 'type' => 'field_sql_storage', - ), - ); - field_create_field($field_definition); - - // Test disabling and enabling: - // - the field type module, - // - the storage module, - // - both. - $this->_testActiveHelper($field_definition, array('field_test')); - $this->_testActiveHelper($field_definition, array('field_sql_storage')); - $this->_testActiveHelper($field_definition, array('field_test', 'field_sql_storage')); - } - - /** - * Helper function for testActive(). - * - * Test dependency between a field and a set of modules. - * - * @param $field_definition - * A field definition. - * @param $modules - * An aray of module names. The field will be tested to be inactive as long - * as any of those modules is disabled. - */ - function _testActiveHelper($field_definition, $modules) { - $field_name = $field_definition['field_name']; - - // Read the field. - $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, t('The field was properly read.')); - - module_disable($modules, FALSE); - - $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE)); - $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.')); - - // Re-enable modules one by one, and check that the field is still inactive - // while some modules remain disabled. - while ($modules) { - $field = field_read_field($field_name); - $this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules)))); - - $module = array_shift($modules); - module_enable(array($module), FALSE); - } - - // Check that the field is active again after all modules have been - // enabled. - $field = field_read_field($field_name); - $this->assertTrue($field_definition <= $field, t('The field was was marked active.')); - } -} - -class FieldInstanceCrudTestCase extends FieldTestCase { - protected $field; - - public static function getInfo() { - return array( - 'name' => 'Field instance CRUD tests', - 'description' => 'Create field entities by attaching fields to entities.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('field_test'); - - $this->field = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'test_field', - ); - field_create_field($this->field); - $this->instance_definition = array( - 'field_name' => $this->field['field_name'], - 'entity_type' => 'test_entity', - 'bundle' => 'test_bundle', - ); - } - - // TODO : test creation with - // - a full fledged $instance structure, check that all the values are there - // - a minimal $instance structure, check all default values are set - // defer actual $instance comparison to a helper function, used for the two cases above, - // and for testUpdateFieldInstance - - /** - * Test the creation of a field instance. - */ - function testCreateFieldInstance() { - field_create_instance($this->instance_definition); - - // Read the raw record from the {field_config_instance} table. - $result = db_query('SELECT * FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $this->instance_definition['field_name'], ':bundle' => $this->instance_definition['bundle'])); - $record = $result->fetchAssoc(); - $record['data'] = unserialize($record['data']); - - $field_type = field_info_field_types($this->field['type']); - $widget_type = field_info_widget_types($field_type['default_widget']); - $formatter_type = field_info_formatter_types($field_type['default_formatter']); - - // Check that default values are set. - $this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.')); - $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.')); - $this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.')); - $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.')); - $this->assertTrue(isset($record['data']['display']['default']), t('Display for "full" view_mode has been written.')); - $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.')); - - // Check that default settings are set. - $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.')); - $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.')); - $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_mode have been written.')); - - // Guarantee that the field/bundle combination is unique. - try { - field_create_instance($this->instance_definition); - $this->fail(t('Cannot create two instances with the same field / bundle combination.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create two instances with the same field / bundle combination.')); - } - - // Check that the specified field exists. - try { - $this->instance_definition['field_name'] = $this->randomName(); - field_create_instance($this->instance_definition); - $this->fail(t('Cannot create an instance of a non-existing field.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create an instance of a non-existing field.')); - } - - // Create a field restricted to a specific entity type. - $field_restricted = array( - 'field_name' => drupal_strtolower($this->randomName()), - 'type' => 'test_field', - 'entity_types' => array('test_cacheable_entity'), - ); - field_create_field($field_restricted); - - // Check that an instance can be added to an entity type allowed - // by the field. - try { - $instance = $this->instance_definition; - $instance['field_name'] = $field_restricted['field_name']; - $instance['entity_type'] = 'test_cacheable_entity'; - field_create_instance($instance); - $this->pass(t('Can create an instance on an entity type allowed by the field.')); - } - catch (FieldException $e) { - $this->fail(t('Can create an instance on an entity type allowed by the field.')); - } - - // Check that an instance cannot be added to an entity type - // forbidden by the field. - try { - $instance = $this->instance_definition; - $instance['field_name'] = $field_restricted['field_name']; - field_create_instance($instance); - $this->fail(t('Cannot create an instance on an entity type forbidden by the field.')); - } - catch (FieldException $e) { - $this->pass(t('Cannot create an instance on an entity type forbidden by the field.')); - } - - // TODO: test other failures. - } - - /** - * Test reading back an instance definition. - */ - function testReadFieldInstance() { - field_create_instance($this->instance_definition); - - // Read the instance back. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue($this->instance_definition < $instance, t('The field was properly read.')); - } - - /** - * Test the update of a field instance. - */ - function testUpdateFieldInstance() { - field_create_instance($this->instance_definition); - $field_type = field_info_field_types($this->field['type']); - - // Check that basic changes are saved. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $instance['required'] = !$instance['required']; - $instance['label'] = $this->randomName(); - $instance['description'] = $this->randomName(); - $instance['settings']['test_instance_setting'] = $this->randomName(); - $instance['widget']['settings']['test_widget_setting'] =$this->randomName(); - $instance['widget']['weight']++; - $instance['display']['default']['settings']['test_formatter_setting'] = $this->randomName(); - $instance['display']['default']['weight']++; - field_update_instance($instance); - - $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertEqual($instance['required'], $instance_new['required'], t('"required" change is saved')); - $this->assertEqual($instance['label'], $instance_new['label'], t('"label" change is saved')); - $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved')); - $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved')); - $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved')); - $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); - $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], t('Widget weight change is saved')); - - // Check that changing widget and formatter types updates the default settings. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $instance['widget']['type'] = 'test_field_widget_multiple'; - $instance['display']['default']['type'] = 'field_test_multiple'; - field_update_instance($instance); - - $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.')); - $settings = field_info_widget_settings($instance_new['widget']['type']); - $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.')); - $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , t('Formatter type change is saved.')); - $info = field_info_formatter_types($instance_new['display']['default']['type']); - $settings = $info['settings']; - $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , t('Changing formatter type updates default settings.')); - - // Check that adding a new view mode is saved and gets default settings. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $instance['display']['teaser'] = array(); - field_update_instance($instance); - - $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(isset($instance_new['display']['teaser']), t('Display for the new view_mode has been written.')); - $this->assertIdentical($instance_new['display']['teaser']['type'], $field_type['default_formatter'], t('Default formatter for the new view_mode has been written.')); - $info = field_info_formatter_types($instance_new['display']['teaser']['type']); - $settings = $info['settings']; - $this->assertIdentical($settings, $instance_new['display']['teaser']['settings'] , t('Default formatter settings for the new view_mode have been written.')); - - // TODO: test failures. - } - - /** - * Test the deletion of a field instance. - */ - function testDeleteFieldInstance() { - // TODO: Test deletion of the data stored in the field also. - // Need to check that data for a 'deleted' field / instance doesn't get loaded - // Need to check data marked deleted is cleaned on cron (not implemented yet...) - - // Create two instances for the same field so we can test that only one - // is deleted. - field_create_instance($this->instance_definition); - $this->another_instance_definition = $this->instance_definition; - $this->another_instance_definition['bundle'] .= '_another_bundle'; - $instance = field_create_instance($this->another_instance_definition); - - // Test that the first instance is not deleted, and then delete it. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.')); - field_delete_instance($instance); - - // Make sure the instance is marked as deleted when the instance is - // specifically loaded. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE)); - $this->assertTrue(!empty($instance['deleted']), t('A deleted field instance is marked for deletion.')); - - // Try to load the instance normally and make sure it does not show up. - $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); - $this->assertTrue(empty($instance), t('A deleted field instance is not loaded by default.')); - - // Make sure the other field instance is not deleted. - $another_instance = field_read_instance('test_entity', $this->another_instance_definition['field_name'], $this->another_instance_definition['bundle']); - $this->assertTrue(!empty($another_instance) && empty($another_instance['deleted']), t('A non-deleted field instance is not marked for deletion.')); - } -} - -/** - * Unit test class for the multilanguage fields logic. - * - * The following tests will check the multilanguage logic of _field_invoke() and - * that only the correct values are returned by field_available_languages(). - */ -class FieldTranslationsTestCase extends FieldTestCase { - public static function getInfo() { - return array( - 'name' => 'Field translations tests', - 'description' => 'Test multilanguage fields logic.', - 'group' => 'Field API', - ); - } - - function setUp() { - parent::setUp('locale', 'field_test'); - - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - - $this->entity_type = 'test_entity'; - - $field = array( - 'field_name' => $this->field_name, - 'type' => 'test_field', - 'cardinality' => 4, - 'translatable' => TRUE, - ); - field_create_field($field); - $this->field = field_read_field($this->field_name); - - $instance = array( - 'field_name' => $this->field_name, - 'entity_type' => $this->entity_type, - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - $this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle'); - - require_once DRUPAL_ROOT . '/includes/locale.inc'; - for ($i = 0; $i < 3; ++$i) { - locale_add_language('l' . $i, $this->randomString(), $this->randomString()); - } - } - - /** - * Ensures that only valid values are returned by field_available_languages(). - */ - function testFieldAvailableLanguages() { - // Test 'translatable' fieldable info. - field_test_entity_info_translatable('test_entity', FALSE); - $field = $this->field; - $field['field_name'] .= '_untranslatable'; - - // Enable field translations for the entity. - field_test_entity_info_translatable('test_entity', TRUE); - - // Test hook_field_languages() invocation on a translatable field. - variable_set('field_test_field_available_languages_alter', TRUE); - $enabled_languages = field_content_languages(); - $available_languages = field_available_languages($this->entity_type, $this->field); - foreach ($available_languages as $delta => $langcode) { - if ($langcode != 'xx' && $langcode != 'en') { - $this->assertTrue(in_array($langcode, $enabled_languages), t('%language is an enabled language.', array('%language' => $langcode))); - } - } - $this->assertTrue(in_array('xx', $available_languages), t('%language was made available.', array('%language' => 'xx'))); - $this->assertFalse(in_array('en', $available_languages), t('%language was made unavailable.', array('%language' => 'en'))); - - // Test field_available_languages() behavior for untranslatable fields. - $this->field['translatable'] = FALSE; - $this->field_name = $this->field['field_name'] = $this->instance['field_name'] = drupal_strtolower($this->randomName() . '_field_name'); - $available_languages = field_available_languages($this->entity_type, $this->field); - $this->assertTrue(count($available_languages) == 1 && $available_languages[0] === LANGUAGE_NONE, t('For untranslatable fields only LANGUAGE_NONE is available.')); - } - - /** - * Test the multilanguage logic of _field_invoke(). - */ - function testFieldInvoke() { - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']); - - // Populate some extra languages to check if _field_invoke() correctly uses - // the result of field_available_languages(). - $values = array(); - $extra_languages = mt_rand(1, 4); - $languages = $available_languages = field_available_languages($this->entity_type, $this->field); - for ($i = 0; $i < $extra_languages; ++$i) { - $languages[] = $this->randomString(2); - } - - // For each given language provide some random values. - foreach ($languages as $langcode) { - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$langcode][$delta]['value'] = mt_rand(1, 127); - } - } - $entity->{$this->field_name} = $values; - - $results = _field_invoke('test_op', $entity_type, $entity); - foreach ($results as $langcode => $result) { - $hash = hash('sha256', serialize(array($entity_type, $entity, $this->field_name, $langcode, $values[$langcode]))); - // Check whether the parameters passed to _field_invoke() were correctly - // forwarded to the callback function. - $this->assertEqual($hash, $result, t('The result for %language is correctly stored.', array('%language' => $langcode))); - } - $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed.')); - } - - /** - * Test the multilanguage logic of _field_invoke_multiple(). - */ - function testFieldInvokeMultiple() { - $values = array(); - $entities = array(); - $entity_type = 'test_entity'; - $entity_count = mt_rand(1, 5); - $available_languages = field_available_languages($this->entity_type, $this->field); - - for ($id = 1; $id <= $entity_count; ++$id) { - $entity = field_test_create_stub_entity($id, $id, $this->instance['bundle']); - $languages = $available_languages; - - // Populate some extra languages to check whether _field_invoke() - // correctly uses the result of field_available_languages(). - $extra_languages = mt_rand(1, 4); - for ($i = 0; $i < $extra_languages; ++$i) { - $languages[] = $this->randomString(2); - } - - // For each given language provide some random values. - foreach ($languages as $langcode) { - for ($delta = 0; $delta < $this->field['cardinality']; $delta++) { - $values[$id][$langcode][$delta]['value'] = mt_rand(1, 127); - } - } - $entity->{$this->field_name} = $values[$id]; - $entities[$id] = $entity; - } - - $grouped_results = _field_invoke_multiple('test_op_multiple', $entity_type, $entities); - foreach ($grouped_results as $id => $results) { - foreach ($results as $langcode => $result) { - $hash = hash('sha256', serialize(array($entity_type, $entities[$id], $this->field_name, $langcode, $values[$id][$langcode]))); - // Check whether the parameters passed to _field_invoke() were correctly - // forwarded to the callback function. - $this->assertEqual($hash, $result, t('The result for entity %id/%language is correctly stored.', array('%id' => $id, '%language' => $langcode))); - } - $this->assertEqual(count($results), count($available_languages), t('No unavailable language has been processed for entity %id.', array('%id' => $id))); - } - } - - /** - * Test translatable fields storage/retrieval. - */ - function testTranslatableFieldSaveLoad() { - // Enable field translations for nodes. - field_test_entity_info_translatable('node', TRUE); - $entity_info = entity_get_info('node'); - $this->assertTrue(count($entity_info['translation']), t('Nodes are translatable.')); - - // Prepare the field translations. - field_test_entity_info_translatable('test_entity', TRUE); - $eid = $evid = 1; - $entity_type = 'test_entity'; - $entity = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']); - $field_translations = array(); - $available_languages = field_available_languages($entity_type, $this->field); - $this->assertTrue(count($available_languages) > 1, t('Field is translatable.')); - foreach ($available_languages as $langcode) { - $field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']); - } - - // Save and reload the field translations. - $entity->{$this->field_name} = $field_translations; - field_attach_insert($entity_type, $entity); - unset($entity->{$this->field_name}); - field_attach_load($entity_type, array($eid => $entity)); - - // Check if the correct values were saved/loaded. - foreach ($field_translations as $langcode => $items) { - $result = TRUE; - foreach ($items as $delta => $item) { - $result = $result && $item['value'] == $entity->{$this->field_name}[$langcode][$delta]['value']; - } - $this->assertTrue($result, t('%language translation correctly handled.', array('%language' => $langcode))); - } - } - - /** - * Tests display language logic for translatable fields. - */ - function testFieldDisplayLanguage() { - $field_name = drupal_strtolower($this->randomName() . '_field_name'); - $entity_type = 'test_entity'; - - // We need an additional field here to properly test display language - // suggestions. - $field = array( - 'field_name' => $field_name, - 'type' => 'test_field', - 'cardinality' => 2, - 'translatable' => TRUE, - ); - field_create_field($field); - - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => $entity_type, - 'bundle' => 'test_bundle', - ); - field_create_instance($instance); - - $entity = field_test_create_stub_entity(1, 1, $this->instance['bundle']); - list(, , $bundle) = entity_extract_ids($entity_type, $entity); - $instances = field_info_instances($entity_type, $bundle); - - $enabled_languages = field_content_languages(); - $languages = array(); - - // Generate field translations for languages different from the first - // enabled. - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $field = field_info_field($field_name); - do { - // Index 0 is reserved for the requested language, this way we ensure - // that no field is actually populated with it. - $langcode = $enabled_languages[mt_rand(1, count($enabled_languages) - 1)]; - } - while (isset($languages[$langcode])); - $languages[$langcode] = TRUE; - $entity->{$field_name}[$langcode] = $this->_generateTestFieldValues($field['cardinality']); - } - - // Test multiple-fields display languages for untranslatable entities. - field_test_entity_info_translatable($entity_type, FALSE); - drupal_static_reset('field_language'); - $requested_language = $enabled_languages[0]; - $display_language = field_language($entity_type, $entity, NULL, $requested_language); - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $this->assertTrue($display_language[$field_name] == LANGUAGE_NONE, t('The display language for field %field_name is %language.', array('%field_name' => $field_name, '%language' => LANGUAGE_NONE))); - } - - // Test multiple-fields display languages for translatable entities. - field_test_entity_info_translatable($entity_type, TRUE); - drupal_static_reset('field_language'); - $display_language = field_language($entity_type, $entity, NULL, $requested_language); - - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $langcode = $display_language[$field_name]; - // As the requested language was not assinged to any field, if the - // returned language is defined for the current field, core fallback rules - // were successfully applied. - $this->assertTrue(isset($entity->{$field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); - } - - // Test single-field display language. - drupal_static_reset('field_language'); - $langcode = field_language($entity_type, $entity, $this->field_name, $requested_language); - $this->assertTrue(isset($entity->{$this->field_name}[$langcode]) && $langcode != $requested_language, t('The display language for the (single) field %field_name is %language.', array('%field_name' => $field_name, '%language' => $langcode))); - } - - /** - * Tests field translations when creating a new revision. - */ - function testFieldFormTranslationRevisions() { - $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content')); - $this->drupalLogin($web_user); - - // Prepare the field translations. - field_test_entity_info_translatable($this->entity_type, TRUE); - $eid = 1; - $entity = field_test_create_stub_entity($eid, $eid, $this->instance['bundle']); - $available_languages = array_flip(field_available_languages($this->entity_type, $this->field)); - unset($available_languages[LANGUAGE_NONE]); - $field_name = $this->field['field_name']; - - // Store the field translations. - $entity->is_new = TRUE; - foreach ($available_languages as $langcode => $value) { - $entity->{$field_name}[$langcode][0]['value'] = $value + 1; - } - field_test_entity_save($entity); - - // Create a new revision. - $langcode = field_valid_language(NULL); - $edit = array("{$field_name}[$langcode][0][value]" => $entity->{$field_name}[$langcode][0]['value'], 'revision' => TRUE); - $this->drupalPost('test-entity/' . $eid . '/edit', $edit, t('Save')); - - // Check translation revisions. - $this->checkTranslationRevisions($eid, $eid, $available_languages); - $this->checkTranslationRevisions($eid, $eid + 1, $available_languages); - } - - /** - * Check if the field translation attached to the entity revision identified - * by the passed arguments were correctly stored. - */ - private function checkTranslationRevisions($eid, $evid, $available_languages) { - $field_name = $this->field['field_name']; - $entity = field_test_entity_test_load($eid, $evid); - foreach ($available_languages as $langcode => $value) { - $passed = isset($entity->{$field_name}[$langcode]) && $entity->{$field_name}[$langcode][0]['value'] == $value + 1; - $this->assertTrue($passed, t('The @language translation for revision @revision was correctly stored', array('@language' => $langcode, '@revision' => $entity->ftvid))); - } - } -} - -/** - * Unit test class for field bulk delete and batch purge functionality. - */ -class FieldBulkDeleteTestCase extends FieldTestCase { - protected $field; - - public static function getInfo() { - return array( - 'name' => 'Field bulk delete tests', - 'description'=> 'Bulk delete fields and instances, and clean up afterwards.', - 'group' => 'Field API', - ); - } - - /** - * Convenience function for Field API tests. - * - * Given an array of potentially fully-populated entities and an - * optional field name, generate an array of stub entities of the - * same fieldable type which contains the data for the field name - * (if given). - * - * @param $entity_type - * The entity type of $entities. - * @param $entities - * An array of entities of type $entity_type. - * @param $field_name - * Optional; a field name whose data should be copied from - * $entities into the returned stub entities. - * @return - * An array of stub entities corresponding to $entities. - */ - function _generateStubEntities($entity_type, $entities, $field_name = NULL) { - $stubs = array(); - foreach ($entities as $entity) { - $stub = entity_create_stub_entity($entity_type, entity_extract_ids($entity_type, $entity)); - if (isset($field_name)) { - $stub->{$field_name} = $entity->{$field_name}; - } - $stubs[] = $stub; - } - return $stubs; - } - - function setUp() { - parent::setUp('field_test'); - - // Clean up data from previous test cases. - $this->fields = array(); - $this->instances = array(); - - // Create two bundles. - $this->bundles = array('bb_1' => 'bb_1', 'bb_2' => 'bb_2'); - foreach ($this->bundles as $name => $desc) { - field_test_create_bundle($name, $desc); - } - - // Create two fields. - $field = array('field_name' => 'bf_1', 'type' => 'test_field', 'cardinality' => 1); - $this->fields[] = field_create_field($field); - $field = array('field_name' => 'bf_2', 'type' => 'test_field', 'cardinality' => 4); - $this->fields[] = field_create_field($field); - - // For each bundle, create an instance of each field, and 10 - // entities with values for each field. - $id = 0; - $this->entity_type = 'test_entity'; - foreach ($this->bundles as $bundle) { - foreach ($this->fields as $field) { - $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => $this->entity_type, - 'bundle' => $bundle, - 'widget' => array( - 'type' => 'test_field_widget', - ) - ); - $this->instances[] = field_create_instance($instance); - } - - for ($i = 0; $i < 10; $i++) { - $entity = field_test_create_stub_entity($id, $id, $bundle); - foreach ($this->fields as $field) { - $entity->{$field['field_name']}[LANGUAGE_NONE] = $this->_generateTestFieldValues($field['cardinality']); - } - $this->entities[$id] = $entity; - field_attach_insert($this->entity_type, $entity); - $id++; - } - } - } - - /** - * Verify that deleting an instance leaves the field data items in - * the database and that the appropriate Field API functions can - * operate on the deleted data and instance. - * - * This tests how EntityFieldQuery interacts with - * field_delete_instance() and could be moved to FieldCrudTestCase, - * but depends on this class's setUp(). - */ - function testDeleteFieldInstance() { - $bundle = reset($this->bundles); - $field = reset($this->fields); - - // There are 10 entities of this bundle. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->execute(); - $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found before deleting'); - - // Delete the instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); - - // The instance still exists, deleted. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($instances), 1, 'There is one deleted instance'); - $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle'); - - // There are 0 entities of this bundle with non-deleted data. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->execute(); - $this->assertTrue(!isset($found['test_entity']), 'No entities found after deleting'); - - // There are 10 entities of this bundle when deleted fields are allowed, and - // their values are correct. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->deleted(TRUE) - ->execute(); - field_attach_load($this->entity_type, $found[$this->entity_type], FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); - $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found after deleting'); - foreach ($found['test_entity'] as $id => $entity) { - $this->assertEqual($this->entities[$id]->{$field['field_name']}, $entity->{$field['field_name']}, "Entity $id with deleted data loaded correctly"); - } - } - - /** - * Verify that field data items and instances are purged when an - * instance is deleted. - */ - function testPurgeInstance() { - field_test_memorize(); - - $bundle = reset($this->bundles); - $field = reset($this->fields); - - // Delete the instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); - - // No field hooks were called. - $mem = field_test_memorize(); - $this->assertEqual(count($mem), 0, 'No field hooks were called'); - - $batch_size = 2; - for ($count = 8; $count >= 0; $count -= 2) { - // Purge two entities. - field_purge_batch($batch_size); - - // There are $count deleted entities left. - $query = new EntityFieldQuery(); - $found = $query - ->fieldCondition($field) - ->entityCondition('bundle', $bundle) - ->deleted(TRUE) - ->execute(); - $this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2'); - } - - // hook_field_delete() was called on a pseudo-entity for each entity. Each - // pseudo entity has a $field property that matches the original entity, - // but no others. - $mem = field_test_memorize(); - $this->assertEqual(count($mem['field_test_field_delete']), 10, 'hook_field_delete was called for the right number of entities'); - $stubs = $this->_generateStubEntities($this->entity_type, $this->entities, $field['field_name']); - $count = count($stubs); - foreach ($mem['field_test_field_delete'] as $args) { - $entity = $args[1]; - $this->assertEqual($stubs[$entity->ftid], $entity, 'hook_field_delete() called with the correct stub'); - unset($stubs[$entity->ftid]); - } - $this->assertEqual(count($stubs), $count-10, 'hook_field_delete was called with each entity once'); - - // The instance still exists, deleted. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($instances), 1, 'There is one deleted instance'); - - // Purge the instance. - field_purge_batch($batch_size); - - // The instance is gone. - $instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($instances), 0, 'The instance is gone'); - - // The field still exists, not deleted, because it has a second instance. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted'); - } - - /** - * Verify that fields are preserved and purged correctly as multiple - * instances are deleted and purged. - */ - function testPurgeField() { - $field = reset($this->fields); - - foreach ($this->bundles as $bundle) { - // Delete the instance. - $instance = field_info_instance($this->entity_type, $field['field_name'], $bundle); - field_delete_instance($instance); - - // Purge the data. - field_purge_batch(10); - - // Purge again to purge the instance. - field_purge_batch(0); - - // The field still exists, not deleted, because it was never deleted. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted'); - } - - // Delete the field. - field_delete_field($field['field_name']); - - // The field still exists, deleted. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual($fields[$field['id']]['deleted'], 1, 'The field exists and is deleted'); - - // Purge the field. - field_purge_batch(0); - - // The field is gone. - $fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1)); - $this->assertEqual(count($fields), 0, 'The field is purged.'); - } -} diff --git modules/file/file.test modules/file/file.test new file mode 100644 index 0000000..51e3b2d --- /dev/null +++ modules/file/file.test @@ -0,0 +1,759 @@ +admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer content types', 'administer nodes', 'bypass node access')); + $this->drupalLogin($this->admin_user); + } + + /** + * Get a sample file of the specified type. + */ + function getTestFile($type_name, $size = NULL) { + // Get a file to upload. + $file = current($this->drupalGetTestFiles($type_name, $size)); + + // Add a filesize property to files as would be read by file_load(). + $file->filesize = filesize($file->uri); + + return $file; + } + + /** + * Create a new file field. + * + * @param $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param $type_name + * The node type that this field will be added to. + * @param $field_settings + * A list of field settings that will be added to the defaults. + * @param $instance_settings + * A list of instance settings that will be added to the instance defaults. + * @param $widget_settings + * A list of widget settings that will be added to the widget defaults. + */ + function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) { + $field = array( + 'field_name' => $name, + 'type' => 'file', + 'settings' => array(), + 'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1, + ); + $field['settings'] = array_merge($field['settings'], $field_settings); + field_create_field($field); + + $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings); + } + + /** + * Attach a file field to an entity. + * + * @param $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param $entity_type + * The entity type this field will be added to. + * @param $bundle + * The bundle this field will be added to. + * @param $field_settings + * A list of field settings that will be added to the defaults. + * @param $instance_settings + * A list of instance settings that will be added to the instance defaults. + * @param $widget_settings + * A list of widget settings that will be added to the widget defaults. + */ + function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) { + $instance = array( + 'field_name' => $name, + 'label' => $name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'required' => !empty($instance_settings['required']), + 'settings' => array(), + 'widget' => array( + 'type' => 'file_generic', + 'settings' => array(), + ), + ); + $instance['settings'] = array_merge($instance['settings'], $instance_settings); + $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); + field_create_instance($instance); + } + + /** + * Update an existing file field with new settings. + */ + function updateFileField($name, $type_name, $instance_settings = array(), $widget_settings = array()) { + $field = field_info_field($name); + $instance = field_info_instance('node', $name, $type_name); + $instance['settings'] = array_merge($instance['settings'], $instance_settings); + $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); + + field_update_instance($instance); + } + + /** + * Upload a file to a node. + */ + function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE) { + $langcode = LANGUAGE_NONE; + $edit = array( + "title" => $this->randomName(), + 'revision' => (string) (int) $new_revision, + ); + + if (is_numeric($nid_or_type)) { + $node = node_load($nid_or_type); + $delta = isset($node->$field_name) ? count($node->$field_name) : 0; + $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']'] = realpath($file->uri); + $this->drupalPost('node/' . $nid_or_type . '/edit', $edit, t('Save')); + } + else { + $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = realpath($file->uri); + $type_name = str_replace('_', '-', $nid_or_type); + $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); + } + + $matches = array(); + preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches); + return isset($matches[1]) ? $matches[1] : FALSE; + } + + /** + * Remove a file from a node. + * + * Note that if replacing a file, it must first be removed then added again. + */ + function removeNodeFile($nid, $new_revision = TRUE) { + $edit = array( + 'revision' => (string) (int) $new_revision, + ); + + $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); + $this->drupalPost(NULL, $edit, t('Save')); + } + + /** + * Replace a file within a node. + */ + function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { + $edit = array( + 'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => realpath($file->uri), + 'revision' => (string) (int) $new_revision, + ); + + $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); + $this->drupalPost(NULL, $edit, t('Save')); + } + + /** + * Assert that a file exists physically on disk. + */ + function assertFileExists($file, $message = NULL) { + $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri)); + $this->assertTrue(is_file($file->uri), $message); + } + + /** + * Assert that a file exists in the database. + */ + function assertFileEntryExists($file, $message = NULL) { + drupal_static_reset('file_load_multiple'); + $db_file = file_load($file->fid); + $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri)); + $this->assertEqual($db_file->uri, $file->uri, $message); + } + + /** + * Assert that a file does not exist on disk. + */ + function assertFileNotExists($file, $message = NULL) { + $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri)); + $this->assertFalse(is_file($file->uri), $message); + } + + /** + * Assert that a file does not exist in the database. + */ + function assertFileEntryNotExists($file, $message) { + drupal_static_reset('file_load_multiple'); + $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri)); + $this->assertFalse(file_load($file->fid), $message); + } + + /** + * Assert that a file's status is set to permanent in the database. + */ + function assertFileIsPermanent($file, $message = NULL) { + $message = isset($message) ? $message : t('File %file is permanent.', array('%file' => $file->uri)); + $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message); + } +} + + +/** + * Test class to test file field upload and remove buttons, with and without AJAX. + */ +class FileFieldWidgetTestCase extends FileFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'File field widget test', + 'description' => 'Test upload and remove buttons, with and without AJAX.', + 'group' => 'File', + ); + } + + /** + * Tests upload and remove buttons, with and without AJAX. + * + * @todo This function currently only tests the "remove" button of a single- + * valued field. Tests should be added for the "upload" button and for each + * button of a multi-valued field. Tests involving multiple AJAX steps on + * the same page will become easier after http://drupal.org/node/789186 + * lands. Testing the "upload" button in AJAX context requires more + * investigation into how jQuery uploads files, so that drupalPostAJAX() can + * emulate that correctly. + */ + function testWidget() { + // Use 'page' instead of 'article', so that the 'article' image field does + // not conflict with this test. If in the future the 'page' type gets its + // own default file or image field, this test can be made more robust by + // using a custom node type. + $type_name = 'page'; + $field_name = strtolower($this->randomName()); + $this->createFileField($field_name, $type_name); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $test_file = $this->getTestFile('text'); + + foreach (array('nojs', 'js') as $type) { + // Create a new node with the uploaded file and ensure it got uploaded + // successfully. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('New file saved to disk on node creation.')); + + // Ensure the edit page has a remove button instead of an upload button. + $this->drupalGet("node/$nid/edit"); + $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('Node with file does not display the "Upload" button.')); + $this->assertFieldByXpath('//input[@type="submit"]', t('Remove'), t('Node with file displays the "Remove" button.')); + + // "Click" the remove button (emulating either a nojs or js submission). + switch ($type) { + case 'nojs': + $this->drupalPost(NULL, array(), t('Remove')); + break; + case 'js': + // @todo This can be simplified after http://drupal.org/node/789186 + // lands. + preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches); + $settings = drupal_json_decode($matches[1]); + $button = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]'); + $button_id = (string) $button[0]['id']; + $this->drupalPostAJAX(NULL, array(), array((string) $button[0]['name'] => (string) $button[0]['value']), $settings['ajax'][$button_id]['url'], array(), array(), NULL, $settings['ajax'][$button_id]); + break; + } + + // Ensure the page now has an upload button instead of a remove button. + $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), t('After clicking the "Remove" button, it is no longer displayed.')); + $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), t('After clicking the "Remove" button, the "Upload" button is displayed.')); + + // Save the node and ensure it does not have the file. + $this->drupalPost(NULL, array(), t('Save')); + $node = node_load($nid, NULL, TRUE); + $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), t('File was successfully removed from the node.')); + } + } +} + +/** + * Test class to test file handling with node revisions. + */ +class FileFieldRevisionTestCase extends FileFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'File field revision test', + 'description' => 'Test creating and deleting revisions with files attached.', + 'group' => 'File', + ); + } + + /** + * Test creating multiple revisions of a node and managing the attached files. + * + * Expected behaviors: + * - Adding a new revision will make another entry in the field table, but + * the original file will not be duplicated. + * - Deleting a revision should not delete the original file if the file + * is in use by another revision. + * - When the last revision that uses a file is deleted, the original file + * should be deleted also. + */ + function testRevisions() { + $type_name = 'article'; + $field_name = strtolower($this->randomName()); + $this->createFileField($field_name, $type_name); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + // Attach the same fields to users. + $this->attachFileField($field_name, 'user', 'user'); + + $test_file = $this->getTestFile('text'); + + // Create a new node with the uploaded file. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + + // Check that the file exists on disk and in the database. + $node = node_load($nid, NULL, TRUE); + $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $node_vid_r1 = $node->vid; + $this->assertFileExists($node_file_r1, t('New file saved to disk on node creation.')); + $this->assertFileEntryExists($node_file_r1, t('File entry exists in database on node creation.')); + $this->assertFileIsPermanent($node_file_r1, t('File is permanent.')); + + // Upload another file to the same node in a new revision. + $this->replaceNodeFile($test_file, $field_name, $nid); + $node = node_load($nid, NULL, TRUE); + $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $node_vid_r2 = $node->vid; + $this->assertFileExists($node_file_r2, t('Replacement file exists on disk after creating new revision.')); + $this->assertFileEntryExists($node_file_r2, t('Replacement file entry exists in database after creating new revision.')); + $this->assertFileIsPermanent($node_file_r2, t('Replacement file is permanent.')); + + // Check that the original file is still in place on the first revision. + $node = node_load($nid, $node_vid_r1, TRUE); + $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], t('Original file still in place after replacing file in new revision.')); + $this->assertFileExists($node_file_r1, t('Original file still in place after replacing file in new revision.')); + $this->assertFileEntryExists($node_file_r1, t('Original file entry still in place after replacing file in new revision')); + $this->assertFileIsPermanent($node_file_r1, t('Original file is still permanent.')); + + // Save a new version of the node without any changes. + // Check that the file is still the same as the previous revision. + $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save')); + $node = node_load($nid, NULL, TRUE); + $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $node_vid_r3 = $node->vid; + $this->assertEqual($node_file_r2, $node_file_r3, t('Previous revision file still in place after creating a new revision without a new file.')); + $this->assertFileIsPermanent($node_file_r3, t('New revision file is permanent.')); + + // Revert to the first revision and check that the original file is active. + $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert')); + $node = node_load($nid, NULL, TRUE); + $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $node_vid_r4 = $node->vid; + $this->assertEqual($node_file_r1, $node_file_r4, t('Original revision file still in place after reverting to the original revision.')); + $this->assertFileIsPermanent($node_file_r4, t('Original revision file still permanent after reverting to the original revision.')); + + // Delete the second revision and check that the file is kept (since it is + // still being used by the third revision). + $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete')); + $this->assertFileExists($node_file_r3, t('Second file is still available after deleting second revision, since it is being used by the third revision.')); + $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting second revision, since it is being used by the third revision.')); + $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting second revision, since it is being used by the third revision.')); + + // Attach the second file to a user. + $user = $this->drupalCreateUser(); + $edit = array(); + $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3; + user_save($user, $edit); + $this->drupalGet('user/' . $user->uid . '/edit'); + + // Delete the third revision and check that the file is not deleted yet. + $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete')); + $this->assertFileExists($node_file_r3, t('Second file is still available after deleting third revision, since it is being used by the user.')); + $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting third revision, since it is being used by the user.')); + $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting third revision, since it is being used by the user.')); + + // Delete the user and check that the file is also deleted. + user_delete($user->uid); + // TODO: This seems like a bug in File API. Clearing the stat cache should + // not be necessary here. The file really is deleted, but stream wrappers + // doesn't seem to think so unless we clear the PHP file stat() cache. + clearstatcache(); + $this->assertFileNotExists($node_file_r3, t('Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.')); + $this->assertFileEntryNotExists($node_file_r3, t('Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.')); + + // Delete the entire node and check that the original file is deleted. + $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete')); + $this->assertFileNotExists($node_file_r1, t('Original file is deleted after deleting the entire node with two revisions remaining.')); + $this->assertFileEntryNotExists($node_file_r1, t('Original file entry is deleted after deleting the entire node with two revisions remaining.')); + } +} + +/** + * Test class to check that formatters are working properly. + */ +class FileFieldDisplayTestCase extends FileFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'File field display tests', + 'description' => 'Test the display of file fields in node and views.', + 'group' => 'File', + ); + } + + /** + * Test normal formatter display on node display. + */ + function testNodeDisplay() { + $field_name = strtolower($this->randomName()); + $type_name = 'article'; + $field_settings = array( + 'display_field' => '1', + 'display_default' => '1', + ); + $instance_settings = array( + 'description_field' => '1', + ); + $widget_settings = array(); + $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $test_file = $this->getTestFile('text'); + + // Create a new node with the uploaded file. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $this->drupalGet('node/' . $nid . '/edit'); + + // Check that the default formatter is displaying with the file name. + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $default_output = theme('file_link', array('file' => $node_file)); + $this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.')); + + // Turn the "display" option off and check that the file is no longer displayed. + $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE); + $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save')); + + $this->assertNoRaw($default_output, t('Field is hidden when "display" option is unchecked.')); + + } +} + +/** + * Test class to check for various validations. + */ +class FileFieldValidateTestCase extends FileFieldTestCase { + protected $field; + protected $node_type; + + public static function getInfo() { + return array( + 'name' => 'File field validation tests', + 'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.', + 'group' => 'File', + ); + } + + /** + * Test required property on file fields. + */ + function testRequired() { + $type_name = 'article'; + $field_name = strtolower($this->randomName()); + $this->createFileField($field_name, $type_name, array(), array('required' => '1')); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $test_file = $this->getTestFile('text'); + + // Try to post a new node without uploading a file. + $langcode = LANGUAGE_NONE; + $edit = array("title" => $this->randomName()); + $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); + $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required file field was empty.')); + + // Create a new node with the uploaded file. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('File exists after uploading to the required field.')); + $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required field.')); + + // Try again with a multiple value field. + field_delete_field($field_name); + $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1')); + + // Try to post a new node without uploading a file in the multivalue field. + $edit = array('title' => $this->randomName()); + $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); + $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required multiple value file field was empty.')); + + // Create a new node with the uploaded file into the multivalue field. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('File exists after uploading to the required multiple value field.')); + $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required multipel value field.')); + + // Remove our file field. + field_delete_field($field_name); + } + + /** + * Test the max file size validator. + */ + function testFileMaxSize() { + $type_name = 'article'; + $field_name = strtolower($this->randomName()); + $this->createFileField($field_name, $type_name, array(), array('required' => '1')); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $small_file = $this->getTestFile('text', 131072); // 128KB. + $large_file = $this->getTestFile('text', 1310720); // 1.2MB + + // Test uploading both a large and small file with different increments. + $sizes = array( + '1M' => 1048576, + '1024K' => 1048576, + '1048576' => 1048576, + ); + + foreach ($sizes as $max_filesize => $file_limit) { + // Set the max file upload size. + $this->updateFileField($field_name, $type_name, array('max_filesize' => $max_filesize)); + $instance = field_info_instance('node', $field_name, $type_name); + + // Create a new node with the small file, which should pass. + $nid = $this->uploadNodeFile($small_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize))); + $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize))); + + // Check that uploading the large file fails (1M limit). + $nid = $this->uploadNodeFile($large_file, $field_name, $type_name); + $error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($large_file->filesize), '%maxsize' => format_size($file_limit))); + $this->assertRaw($error_message, t('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize))); + } + + // Turn off the max filesize. + $this->updateFileField($field_name, $type_name, array('max_filesize' => '')); + + // Upload the big file successfully. + $nid = $this->uploadNodeFile($large_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize)))); + $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize)))); + + // Remove our file field. + field_delete_field($field_name); + } + + /** + * Test the file extension, do additional checks if mimedetect is installed. + */ + function testFileExtension() { + $type_name = 'article'; + $field_name = strtolower($this->randomName()); + $this->createFileField($field_name, $type_name); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $test_file = $this->getTestFile('image'); + list(, $test_file_extension) = explode('.', $test_file->filename); + + // Disable extension checking. + $this->updateFileField($field_name, $type_name, array('file_extensions' => '')); + + // Check that the file can be uploaded with no extension checking. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('File exists after uploading a file with no extension checking.')); + $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with no extension checking.')); + + // Enable extension checking for text files. + $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt')); + + // Check that the file with the wrong extension cannot be uploaded. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt')); + $this->assertRaw($error_message, t('Node save failed when file uploaded with the wrong extension.')); + + // Enable extension checking for text and image files. + $this->updateFileField($field_name, $type_name, array('file_extensions' => "txt $test_file_extension")); + + // Check that the file can be uploaded with extension checking. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('File exists after uploading a file with extension checking.')); + $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with extension checking.')); + + // Remove our file field. + field_delete_field($field_name); + } +} + +/** + * Test class to check that files are uploaded to proper locations. + */ +class FileFieldPathTestCase extends FileFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'File field file path tests', + 'description' => 'Test that files are uploaded to the proper location with token support.', + 'group' => 'File', + ); + } + + /** + * Test normal formatter display on node display. + */ + function testUploadPath() { + $field_name = strtolower($this->randomName()); + $type_name = 'article'; + $field = $this->createFileField($field_name, $type_name); + $test_file = $this->getTestFile('text'); + + // Create a new node. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + + // Check that the file was uploaded to the file root. + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); + + // Change the path to contain multiple subdirectories. + $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz')); + + // Upload a new file into the subdirectories. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + + // Check that the file was uploaded into the subdirectory. + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertPathMatch('public://foo/bar/baz/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); + + // Check the path when used with tokens. + // Change the path to contain multiple token directories. + $field = $this->updateFileField($field_name, $type_name, array('file_directory' => '[user:uid]/[user:name]')); + + // Upload a new file into the token subdirectories. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + + // Check that the file was uploaded into the subdirectory. + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $data = array('user' => $this->admin_user); + $subdirectory = token_replace('[user:uid]/[user:name]', $data); + $this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file->uri))); + } + + /** + * A loose assertion to check that a file is uploaded to the right location. + * + * @param $expected_path + * The location where the file is expected to be uploaded. Duplicate file + * names to not need to be taken into account. + * @param $actual_path + * Where the file was actually uploaded. + * @param $message + * The message to display with this assertion. + */ + function assertPathMatch($expected_path, $actual_path, $message) { + // Strip off the extension of the expected path to allow for _0, _1, etc. + // suffixes when the file hits a duplicate name. + $pos = strrpos($expected_path, '.'); + $base_path = substr($expected_path, 0, $pos); + $extension = substr($expected_path, $pos + 1); + + $result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path); + $this->assertTrue($result, $message); + } +} + +/** + * Test file token replacement in strings. + */ +class FileTokenReplaceTestCase extends FileFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'File token replacement', + 'description' => 'Generates text using placeholders for dummy content to check file token replacement.', + 'group' => 'File', + ); + } + + /** + * Creates a file, then tests the tokens generated from it. + */ + function testFileTokenReplacement() { + global $language; + $url_options = array( + 'absolute' => TRUE, + 'language' => $language, + ); + + // Create file field. + $type_name = 'article'; + $field_name = 'field_' . strtolower($this->randomName()); + $this->createFileField($field_name, $type_name); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $test_file = $this->getTestFile('text'); + + // Create a new node with the uploaded file. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + + // Load the node and the file. + $node = node_load($nid); + $file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $file->description = 'File description.'; + + // Generate and test sanitized tokens. + $tests = array(); + $tests['[file:fid]'] = $file->fid; + $tests['[file:name]'] = check_plain($file->filename); + $tests['[file:description]'] = filter_xss($file->description); + $tests['[file:path]'] = filter_xss($file->uri); + $tests['[file:mime]'] = filter_xss($file->filemime); + $tests['[file:size]'] = format_size($file->filesize); + $tests['[file:url]'] = url(file_create_url($file->uri), $url_options); + $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language); + $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language); + $tests['[file:owner]'] = $this->admin_user->name; + $tests['[file:owner:uid]'] = $file->uid; + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('file' => $file), array('language' => $language)); + $this->assertFalse(strcmp($output, $expected), t('Sanitized file token %token replaced.', array('%token' => $input))); + } + + // Generate and test unsanitized tokens. + $tests['[file:name]'] = $file->filename; + $tests['[file:description]'] = $file->description; + $tests['[file:path]'] = $file->uri; + $tests['[file:mime]'] = $file->filemime; + $tests['[file:size]'] = format_size($file->filesize); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('file' => $file), array('language' => $language, 'sanitize' => FALSE)); + $this->assertFalse(strcmp($output, $expected), t('Unsanitized file token %token replaced.', array('%token' => $input))); + } + } +} diff --git modules/file/tests/file.test modules/file/tests/file.test deleted file mode 100644 index 51e3b2d..0000000 --- modules/file/tests/file.test +++ /dev/null @@ -1,759 +0,0 @@ -admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer content types', 'administer nodes', 'bypass node access')); - $this->drupalLogin($this->admin_user); - } - - /** - * Get a sample file of the specified type. - */ - function getTestFile($type_name, $size = NULL) { - // Get a file to upload. - $file = current($this->drupalGetTestFiles($type_name, $size)); - - // Add a filesize property to files as would be read by file_load(). - $file->filesize = filesize($file->uri); - - return $file; - } - - /** - * Create a new file field. - * - * @param $name - * The name of the new field (all lowercase), exclude the "field_" prefix. - * @param $type_name - * The node type that this field will be added to. - * @param $field_settings - * A list of field settings that will be added to the defaults. - * @param $instance_settings - * A list of instance settings that will be added to the instance defaults. - * @param $widget_settings - * A list of widget settings that will be added to the widget defaults. - */ - function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) { - $field = array( - 'field_name' => $name, - 'type' => 'file', - 'settings' => array(), - 'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1, - ); - $field['settings'] = array_merge($field['settings'], $field_settings); - field_create_field($field); - - $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings); - } - - /** - * Attach a file field to an entity. - * - * @param $name - * The name of the new field (all lowercase), exclude the "field_" prefix. - * @param $entity_type - * The entity type this field will be added to. - * @param $bundle - * The bundle this field will be added to. - * @param $field_settings - * A list of field settings that will be added to the defaults. - * @param $instance_settings - * A list of instance settings that will be added to the instance defaults. - * @param $widget_settings - * A list of widget settings that will be added to the widget defaults. - */ - function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) { - $instance = array( - 'field_name' => $name, - 'label' => $name, - 'entity_type' => $entity_type, - 'bundle' => $bundle, - 'required' => !empty($instance_settings['required']), - 'settings' => array(), - 'widget' => array( - 'type' => 'file_generic', - 'settings' => array(), - ), - ); - $instance['settings'] = array_merge($instance['settings'], $instance_settings); - $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); - field_create_instance($instance); - } - - /** - * Update an existing file field with new settings. - */ - function updateFileField($name, $type_name, $instance_settings = array(), $widget_settings = array()) { - $field = field_info_field($name); - $instance = field_info_instance('node', $name, $type_name); - $instance['settings'] = array_merge($instance['settings'], $instance_settings); - $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); - - field_update_instance($instance); - } - - /** - * Upload a file to a node. - */ - function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE) { - $langcode = LANGUAGE_NONE; - $edit = array( - "title" => $this->randomName(), - 'revision' => (string) (int) $new_revision, - ); - - if (is_numeric($nid_or_type)) { - $node = node_load($nid_or_type); - $delta = isset($node->$field_name) ? count($node->$field_name) : 0; - $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']'] = realpath($file->uri); - $this->drupalPost('node/' . $nid_or_type . '/edit', $edit, t('Save')); - } - else { - $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = realpath($file->uri); - $type_name = str_replace('_', '-', $nid_or_type); - $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); - } - - $matches = array(); - preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches); - return isset($matches[1]) ? $matches[1] : FALSE; - } - - /** - * Remove a file from a node. - * - * Note that if replacing a file, it must first be removed then added again. - */ - function removeNodeFile($nid, $new_revision = TRUE) { - $edit = array( - 'revision' => (string) (int) $new_revision, - ); - - $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); - $this->drupalPost(NULL, $edit, t('Save')); - } - - /** - * Replace a file within a node. - */ - function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { - $edit = array( - 'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => realpath($file->uri), - 'revision' => (string) (int) $new_revision, - ); - - $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); - $this->drupalPost(NULL, $edit, t('Save')); - } - - /** - * Assert that a file exists physically on disk. - */ - function assertFileExists($file, $message = NULL) { - $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri)); - $this->assertTrue(is_file($file->uri), $message); - } - - /** - * Assert that a file exists in the database. - */ - function assertFileEntryExists($file, $message = NULL) { - drupal_static_reset('file_load_multiple'); - $db_file = file_load($file->fid); - $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri)); - $this->assertEqual($db_file->uri, $file->uri, $message); - } - - /** - * Assert that a file does not exist on disk. - */ - function assertFileNotExists($file, $message = NULL) { - $message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file->uri)); - $this->assertFalse(is_file($file->uri), $message); - } - - /** - * Assert that a file does not exist in the database. - */ - function assertFileEntryNotExists($file, $message) { - drupal_static_reset('file_load_multiple'); - $message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file->uri)); - $this->assertFalse(file_load($file->fid), $message); - } - - /** - * Assert that a file's status is set to permanent in the database. - */ - function assertFileIsPermanent($file, $message = NULL) { - $message = isset($message) ? $message : t('File %file is permanent.', array('%file' => $file->uri)); - $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message); - } -} - - -/** - * Test class to test file field upload and remove buttons, with and without AJAX. - */ -class FileFieldWidgetTestCase extends FileFieldTestCase { - public static function getInfo() { - return array( - 'name' => 'File field widget test', - 'description' => 'Test upload and remove buttons, with and without AJAX.', - 'group' => 'File', - ); - } - - /** - * Tests upload and remove buttons, with and without AJAX. - * - * @todo This function currently only tests the "remove" button of a single- - * valued field. Tests should be added for the "upload" button and for each - * button of a multi-valued field. Tests involving multiple AJAX steps on - * the same page will become easier after http://drupal.org/node/789186 - * lands. Testing the "upload" button in AJAX context requires more - * investigation into how jQuery uploads files, so that drupalPostAJAX() can - * emulate that correctly. - */ - function testWidget() { - // Use 'page' instead of 'article', so that the 'article' image field does - // not conflict with this test. If in the future the 'page' type gets its - // own default file or image field, this test can be made more robust by - // using a custom node type. - $type_name = 'page'; - $field_name = strtolower($this->randomName()); - $this->createFileField($field_name, $type_name); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - $test_file = $this->getTestFile('text'); - - foreach (array('nojs', 'js') as $type) { - // Create a new node with the uploaded file and ensure it got uploaded - // successfully. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('New file saved to disk on node creation.')); - - // Ensure the edit page has a remove button instead of an upload button. - $this->drupalGet("node/$nid/edit"); - $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('Node with file does not display the "Upload" button.')); - $this->assertFieldByXpath('//input[@type="submit"]', t('Remove'), t('Node with file displays the "Remove" button.')); - - // "Click" the remove button (emulating either a nojs or js submission). - switch ($type) { - case 'nojs': - $this->drupalPost(NULL, array(), t('Remove')); - break; - case 'js': - // @todo This can be simplified after http://drupal.org/node/789186 - // lands. - preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches); - $settings = drupal_json_decode($matches[1]); - $button = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]'); - $button_id = (string) $button[0]['id']; - $this->drupalPostAJAX(NULL, array(), array((string) $button[0]['name'] => (string) $button[0]['value']), $settings['ajax'][$button_id]['url'], array(), array(), NULL, $settings['ajax'][$button_id]); - break; - } - - // Ensure the page now has an upload button instead of a remove button. - $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), t('After clicking the "Remove" button, it is no longer displayed.')); - $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), t('After clicking the "Remove" button, the "Upload" button is displayed.')); - - // Save the node and ensure it does not have the file. - $this->drupalPost(NULL, array(), t('Save')); - $node = node_load($nid, NULL, TRUE); - $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), t('File was successfully removed from the node.')); - } - } -} - -/** - * Test class to test file handling with node revisions. - */ -class FileFieldRevisionTestCase extends FileFieldTestCase { - public static function getInfo() { - return array( - 'name' => 'File field revision test', - 'description' => 'Test creating and deleting revisions with files attached.', - 'group' => 'File', - ); - } - - /** - * Test creating multiple revisions of a node and managing the attached files. - * - * Expected behaviors: - * - Adding a new revision will make another entry in the field table, but - * the original file will not be duplicated. - * - Deleting a revision should not delete the original file if the file - * is in use by another revision. - * - When the last revision that uses a file is deleted, the original file - * should be deleted also. - */ - function testRevisions() { - $type_name = 'article'; - $field_name = strtolower($this->randomName()); - $this->createFileField($field_name, $type_name); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - // Attach the same fields to users. - $this->attachFileField($field_name, 'user', 'user'); - - $test_file = $this->getTestFile('text'); - - // Create a new node with the uploaded file. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - - // Check that the file exists on disk and in the database. - $node = node_load($nid, NULL, TRUE); - $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $node_vid_r1 = $node->vid; - $this->assertFileExists($node_file_r1, t('New file saved to disk on node creation.')); - $this->assertFileEntryExists($node_file_r1, t('File entry exists in database on node creation.')); - $this->assertFileIsPermanent($node_file_r1, t('File is permanent.')); - - // Upload another file to the same node in a new revision. - $this->replaceNodeFile($test_file, $field_name, $nid); - $node = node_load($nid, NULL, TRUE); - $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $node_vid_r2 = $node->vid; - $this->assertFileExists($node_file_r2, t('Replacement file exists on disk after creating new revision.')); - $this->assertFileEntryExists($node_file_r2, t('Replacement file entry exists in database after creating new revision.')); - $this->assertFileIsPermanent($node_file_r2, t('Replacement file is permanent.')); - - // Check that the original file is still in place on the first revision. - $node = node_load($nid, $node_vid_r1, TRUE); - $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], t('Original file still in place after replacing file in new revision.')); - $this->assertFileExists($node_file_r1, t('Original file still in place after replacing file in new revision.')); - $this->assertFileEntryExists($node_file_r1, t('Original file entry still in place after replacing file in new revision')); - $this->assertFileIsPermanent($node_file_r1, t('Original file is still permanent.')); - - // Save a new version of the node without any changes. - // Check that the file is still the same as the previous revision. - $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save')); - $node = node_load($nid, NULL, TRUE); - $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $node_vid_r3 = $node->vid; - $this->assertEqual($node_file_r2, $node_file_r3, t('Previous revision file still in place after creating a new revision without a new file.')); - $this->assertFileIsPermanent($node_file_r3, t('New revision file is permanent.')); - - // Revert to the first revision and check that the original file is active. - $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert')); - $node = node_load($nid, NULL, TRUE); - $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $node_vid_r4 = $node->vid; - $this->assertEqual($node_file_r1, $node_file_r4, t('Original revision file still in place after reverting to the original revision.')); - $this->assertFileIsPermanent($node_file_r4, t('Original revision file still permanent after reverting to the original revision.')); - - // Delete the second revision and check that the file is kept (since it is - // still being used by the third revision). - $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete')); - $this->assertFileExists($node_file_r3, t('Second file is still available after deleting second revision, since it is being used by the third revision.')); - $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting second revision, since it is being used by the third revision.')); - $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting second revision, since it is being used by the third revision.')); - - // Attach the second file to a user. - $user = $this->drupalCreateUser(); - $edit = array(); - $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3; - user_save($user, $edit); - $this->drupalGet('user/' . $user->uid . '/edit'); - - // Delete the third revision and check that the file is not deleted yet. - $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete')); - $this->assertFileExists($node_file_r3, t('Second file is still available after deleting third revision, since it is being used by the user.')); - $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting third revision, since it is being used by the user.')); - $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting third revision, since it is being used by the user.')); - - // Delete the user and check that the file is also deleted. - user_delete($user->uid); - // TODO: This seems like a bug in File API. Clearing the stat cache should - // not be necessary here. The file really is deleted, but stream wrappers - // doesn't seem to think so unless we clear the PHP file stat() cache. - clearstatcache(); - $this->assertFileNotExists($node_file_r3, t('Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.')); - $this->assertFileEntryNotExists($node_file_r3, t('Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.')); - - // Delete the entire node and check that the original file is deleted. - $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete')); - $this->assertFileNotExists($node_file_r1, t('Original file is deleted after deleting the entire node with two revisions remaining.')); - $this->assertFileEntryNotExists($node_file_r1, t('Original file entry is deleted after deleting the entire node with two revisions remaining.')); - } -} - -/** - * Test class to check that formatters are working properly. - */ -class FileFieldDisplayTestCase extends FileFieldTestCase { - public static function getInfo() { - return array( - 'name' => 'File field display tests', - 'description' => 'Test the display of file fields in node and views.', - 'group' => 'File', - ); - } - - /** - * Test normal formatter display on node display. - */ - function testNodeDisplay() { - $field_name = strtolower($this->randomName()); - $type_name = 'article'; - $field_settings = array( - 'display_field' => '1', - 'display_default' => '1', - ); - $instance_settings = array( - 'description_field' => '1', - ); - $widget_settings = array(); - $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - $test_file = $this->getTestFile('text'); - - // Create a new node with the uploaded file. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $this->drupalGet('node/' . $nid . '/edit'); - - // Check that the default formatter is displaying with the file name. - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $default_output = theme('file_link', array('file' => $node_file)); - $this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.')); - - // Turn the "display" option off and check that the file is no longer displayed. - $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE); - $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save')); - - $this->assertNoRaw($default_output, t('Field is hidden when "display" option is unchecked.')); - - } -} - -/** - * Test class to check for various validations. - */ -class FileFieldValidateTestCase extends FileFieldTestCase { - protected $field; - protected $node_type; - - public static function getInfo() { - return array( - 'name' => 'File field validation tests', - 'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.', - 'group' => 'File', - ); - } - - /** - * Test required property on file fields. - */ - function testRequired() { - $type_name = 'article'; - $field_name = strtolower($this->randomName()); - $this->createFileField($field_name, $type_name, array(), array('required' => '1')); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - $test_file = $this->getTestFile('text'); - - // Try to post a new node without uploading a file. - $langcode = LANGUAGE_NONE; - $edit = array("title" => $this->randomName()); - $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); - $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required file field was empty.')); - - // Create a new node with the uploaded file. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('File exists after uploading to the required field.')); - $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required field.')); - - // Try again with a multiple value field. - field_delete_field($field_name); - $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1')); - - // Try to post a new node without uploading a file in the multivalue field. - $edit = array('title' => $this->randomName()); - $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); - $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), t('Node save failed when required multiple value file field was empty.')); - - // Create a new node with the uploaded file into the multivalue field. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('File exists after uploading to the required multiple value field.')); - $this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required multipel value field.')); - - // Remove our file field. - field_delete_field($field_name); - } - - /** - * Test the max file size validator. - */ - function testFileMaxSize() { - $type_name = 'article'; - $field_name = strtolower($this->randomName()); - $this->createFileField($field_name, $type_name, array(), array('required' => '1')); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - $small_file = $this->getTestFile('text', 131072); // 128KB. - $large_file = $this->getTestFile('text', 1310720); // 1.2MB - - // Test uploading both a large and small file with different increments. - $sizes = array( - '1M' => 1048576, - '1024K' => 1048576, - '1048576' => 1048576, - ); - - foreach ($sizes as $max_filesize => $file_limit) { - // Set the max file upload size. - $this->updateFileField($field_name, $type_name, array('max_filesize' => $max_filesize)); - $instance = field_info_instance('node', $field_name, $type_name); - - // Create a new node with the small file, which should pass. - $nid = $this->uploadNodeFile($small_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize))); - $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize))); - - // Check that uploading the large file fails (1M limit). - $nid = $this->uploadNodeFile($large_file, $field_name, $type_name); - $error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($large_file->filesize), '%maxsize' => format_size($file_limit))); - $this->assertRaw($error_message, t('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize))); - } - - // Turn off the max filesize. - $this->updateFileField($field_name, $type_name, array('max_filesize' => '')); - - // Upload the big file successfully. - $nid = $this->uploadNodeFile($large_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize)))); - $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize)))); - - // Remove our file field. - field_delete_field($field_name); - } - - /** - * Test the file extension, do additional checks if mimedetect is installed. - */ - function testFileExtension() { - $type_name = 'article'; - $field_name = strtolower($this->randomName()); - $this->createFileField($field_name, $type_name); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - $test_file = $this->getTestFile('image'); - list(, $test_file_extension) = explode('.', $test_file->filename); - - // Disable extension checking. - $this->updateFileField($field_name, $type_name, array('file_extensions' => '')); - - // Check that the file can be uploaded with no extension checking. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('File exists after uploading a file with no extension checking.')); - $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with no extension checking.')); - - // Enable extension checking for text files. - $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt')); - - // Check that the file with the wrong extension cannot be uploaded. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt')); - $this->assertRaw($error_message, t('Node save failed when file uploaded with the wrong extension.')); - - // Enable extension checking for text and image files. - $this->updateFileField($field_name, $type_name, array('file_extensions' => "txt $test_file_extension")); - - // Check that the file can be uploaded with extension checking. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertFileExists($node_file, t('File exists after uploading a file with extension checking.')); - $this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with extension checking.')); - - // Remove our file field. - field_delete_field($field_name); - } -} - -/** - * Test class to check that files are uploaded to proper locations. - */ -class FileFieldPathTestCase extends FileFieldTestCase { - public static function getInfo() { - return array( - 'name' => 'File field file path tests', - 'description' => 'Test that files are uploaded to the proper location with token support.', - 'group' => 'File', - ); - } - - /** - * Test normal formatter display on node display. - */ - function testUploadPath() { - $field_name = strtolower($this->randomName()); - $type_name = 'article'; - $field = $this->createFileField($field_name, $type_name); - $test_file = $this->getTestFile('text'); - - // Create a new node. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - - // Check that the file was uploaded to the file root. - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); - - // Change the path to contain multiple subdirectories. - $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz')); - - // Upload a new file into the subdirectories. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - - // Check that the file was uploaded into the subdirectory. - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $this->assertPathMatch('public://foo/bar/baz/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); - - // Check the path when used with tokens. - // Change the path to contain multiple token directories. - $field = $this->updateFileField($field_name, $type_name, array('file_directory' => '[user:uid]/[user:name]')); - - // Upload a new file into the token subdirectories. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - - // Check that the file was uploaded into the subdirectory. - $node = node_load($nid, NULL, TRUE); - $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $data = array('user' => $this->admin_user); - $subdirectory = token_replace('[user:uid]/[user:name]', $data); - $this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->filename, $node_file->uri, t('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file->uri))); - } - - /** - * A loose assertion to check that a file is uploaded to the right location. - * - * @param $expected_path - * The location where the file is expected to be uploaded. Duplicate file - * names to not need to be taken into account. - * @param $actual_path - * Where the file was actually uploaded. - * @param $message - * The message to display with this assertion. - */ - function assertPathMatch($expected_path, $actual_path, $message) { - // Strip off the extension of the expected path to allow for _0, _1, etc. - // suffixes when the file hits a duplicate name. - $pos = strrpos($expected_path, '.'); - $base_path = substr($expected_path, 0, $pos); - $extension = substr($expected_path, $pos + 1); - - $result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path); - $this->assertTrue($result, $message); - } -} - -/** - * Test file token replacement in strings. - */ -class FileTokenReplaceTestCase extends FileFieldTestCase { - public static function getInfo() { - return array( - 'name' => 'File token replacement', - 'description' => 'Generates text using placeholders for dummy content to check file token replacement.', - 'group' => 'File', - ); - } - - /** - * Creates a file, then tests the tokens generated from it. - */ - function testFileTokenReplacement() { - global $language; - $url_options = array( - 'absolute' => TRUE, - 'language' => $language, - ); - - // Create file field. - $type_name = 'article'; - $field_name = 'field_' . strtolower($this->randomName()); - $this->createFileField($field_name, $type_name); - $field = field_info_field($field_name); - $instance = field_info_instance('node', $field_name, $type_name); - - $test_file = $this->getTestFile('text'); - - // Create a new node with the uploaded file. - $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); - - // Load the node and the file. - $node = node_load($nid); - $file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; - $file->description = 'File description.'; - - // Generate and test sanitized tokens. - $tests = array(); - $tests['[file:fid]'] = $file->fid; - $tests['[file:name]'] = check_plain($file->filename); - $tests['[file:description]'] = filter_xss($file->description); - $tests['[file:path]'] = filter_xss($file->uri); - $tests['[file:mime]'] = filter_xss($file->filemime); - $tests['[file:size]'] = format_size($file->filesize); - $tests['[file:url]'] = url(file_create_url($file->uri), $url_options); - $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language); - $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language); - $tests['[file:owner]'] = $this->admin_user->name; - $tests['[file:owner:uid]'] = $file->uid; - - // Test to make sure that we generated something for each token. - $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('file' => $file), array('language' => $language)); - $this->assertFalse(strcmp($output, $expected), t('Sanitized file token %token replaced.', array('%token' => $input))); - } - - // Generate and test unsanitized tokens. - $tests['[file:name]'] = $file->filename; - $tests['[file:description]'] = $file->description; - $tests['[file:path]'] = $file->uri; - $tests['[file:mime]'] = $file->filemime; - $tests['[file:size]'] = format_size($file->filesize); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('file' => $file), array('language' => $language, 'sanitize' => FALSE)); - $this->assertFalse(strcmp($output, $expected), t('Unsanitized file token %token replaced.', array('%token' => $input))); - } - } -} diff --git modules/simpletest/tests/actions.test modules/simpletest/tests/actions.test deleted file mode 100644 index 8e8f176..0000000 --- modules/simpletest/tests/actions.test +++ /dev/null @@ -1,127 +0,0 @@ - 'Actions configuration', - 'description' => 'Tests complex actions configuration by adding, editing, and deleting a complex action.', - 'group' => 'Actions', - ); - } - - /** - * Test the configuration of advanced actions through the administration - * interface. - */ - function testActionConfiguration() { - // Create a user with permission to view the actions administration pages. - $user = $this->drupalCreateUser(array('administer actions')); - $this->drupalLogin($user); - - // Make a POST request to admin/config/system/actions/manage. - $edit = array(); - $edit['action'] = drupal_hash_base64('system_goto_action'); - $this->drupalPost('admin/config/system/actions/manage', $edit, t('Create')); - - // Make a POST request to the individual action configuration page. - $edit = array(); - $action_label = $this->randomName(); - $edit['actions_label'] = $action_label; - $edit['url'] = 'admin'; - $this->drupalPost('admin/config/system/actions/configure/' . drupal_hash_base64('system_goto_action'), $edit, t('Save')); - - // Make sure that the new complex action was saved properly. - $this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully saved the complex action.")); - $this->assertText($action_label, t("Make sure the action label appears on the configuration page after we've saved the complex action.")); - - // Make another POST request to the action edit page. - $this->clickLink(t('configure')); - preg_match('|admin/config/system/actions/configure/(\d+)|', $this->getUrl(), $matches); - $aid = $matches[1]; - $edit = array(); - $new_action_label = $this->randomName(); - $edit['actions_label'] = $new_action_label; - $edit['url'] = 'admin'; - $this->drupalPost(NULL, $edit, t('Save')); - - // Make sure that the action updated properly. - $this->assertText(t('The action has been successfully saved.'), t("Make sure we get a confirmation that we've successfully updated the complex action.")); - $this->assertNoText($action_label, t("Make sure the old action label does NOT appear on the configuration page after we've updated the complex action.")); - $this->assertText($new_action_label, t("Make sure the action label appears on the configuration page after we've updated the complex action.")); - - // Make sure that deletions work properly. - $this->clickLink(t('delete')); - $edit = array(); - $this->drupalPost("admin/config/system/actions/delete/$aid", $edit, t('Delete')); - - // Make sure that the action was actually deleted. - $this->assertRaw(t('Action %action was deleted', array('%action' => $new_action_label)), t('Make sure that we get a delete confirmation message.')); - $this->drupalGet('admin/config/system/actions/manage'); - $this->assertNoText($new_action_label, t("Make sure the action label does not appear on the overview page after we've deleted the action.")); - $exists = db_query('SELECT aid FROM {actions} WHERE callback = :callback', array(':callback' => 'drupal_goto_action'))->fetchField(); - $this->assertFalse($exists, t('Make sure the action is gone from the database after being deleted.')); - } -} - -/** - * Test actions executing in a potential loop, and make sure they abort properly. - */ -class ActionLoopTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Actions executing in a potentially infinite loop', - 'description' => 'Tests actions executing in a loop, and makes sure they abort properly.', - 'group' => 'Actions', - ); - } - - function setUp() { - parent::setUp('dblog', 'trigger', 'actions_loop_test'); - } - - /** - * Set up a loop with 3 - 12 recursions, and see if it aborts properly. - */ - function testActionLoop() { - $user = $this->drupalCreateUser(array('administer actions')); - $this->drupalLogin($user); - - $hash = drupal_hash_base64('actions_loop_test_log'); - $edit = array('aid' => $hash); - $this->drupalPost('admin/structure/trigger/actions_loop_test', $edit, t('Assign')); - - // Delete any existing watchdog messages to clear the plethora of - // "Action added" messages from when Drupal was installed. - db_delete('watchdog')->execute(); - // To prevent this test from failing when xdebug is enabled, the maximum - // recursion level should be kept low enough to prevent the xdebug - // infinite recursion protection mechanism from aborting the request. - // See http://drupal.org/node/587634. - variable_set('actions_max_stack', mt_rand(3, 12)); - $this->triggerActions(); - } - - /** - * Create an infinite loop by causing a watchdog message to be set, - * which causes the actions to be triggered again, up to actions_max_stack - * times. - */ - protected function triggerActions() { - $this->drupalGet('', array('query' => array('trigger_actions_on_watchdog' => TRUE))); - $expected = array(); - $expected[] = 'Triggering action loop'; - for ($i = 1; $i <= variable_get('actions_max_stack', 35); $i++) { - $expected[] = "Test log #$i"; - } - $expected[] = 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.'; - - $result = db_query("SELECT message FROM {watchdog} WHERE type = 'actions_loop_test' OR type = 'actions' ORDER BY wid"); - $loop_started = FALSE; - foreach ($result as $row) { - $expected_message = array_shift($expected); - $this->assertEqual($row->message, $expected_message, t('Expected message %expected, got %message.', array('%expected' => $expected_message, '%message' => $row->message))); - } - $this->assertTrue(empty($expected), t('All expected messages found.')); - } -} diff --git modules/simpletest/tests/ajax.test modules/simpletest/tests/ajax.test deleted file mode 100644 index 2e10b80..0000000 --- modules/simpletest/tests/ajax.test +++ /dev/null @@ -1,329 +0,0 @@ - 'AJAX framework', - 'description' => 'Performs tests on AJAX framework functions.', - 'group' => 'AJAX', - ); - } - - /** - * Test proper passing of JavaScript settings via ajax_render(). - */ - function testAJAXRender() { - $result = $this->drupalGetAJAX('ajax-test/render'); - // Verify that JavaScript settings are contained (always first). - $this->assertIdentical($result[0]['command'], 'settings', t('drupal_add_js() settings are contained first.')); - // Verify that basePath is contained in JavaScript settings. - $this->assertEqual($result[0]['settings']['basePath'], base_path(), t('Base path is contained in JavaScript settings.')); - } - - /** - * Test behavior of ajax_render_error(). - */ - function testAJAXRenderError() { - $result = $this->drupalGetAJAX('ajax-test/render-error'); - // Verify default error message. - $this->assertEqual($result[0]['command'], 'alert', t('ajax_render_error() invokes alert command.')); - $this->assertEqual($result[0]['text'], t('An error occurred while handling the request: The server received invalid input.'), t('Default error message is output.')); - // Verify custom error message. - $edit = array( - 'message' => 'Custom error message.', - ); - $result = $this->drupalGetAJAX('ajax-test/render-error', array('query' => $edit)); - $this->assertEqual($result[0]['text'], $edit['message'], t('Custom error message is output.')); - } -} - -/** - * Tests AJAX framework commands. - */ -class AJAXCommandsTestCase extends AJAXTestCase { - public static function getInfo() { - return array( - 'name' => 'AJAX commands', - 'description' => 'Performs tests on AJAX framework commands.', - 'group' => 'AJAX', - ); - } - - /** - * Test ajax_command_settings(). - */ - function testAJAXRender() { - $commands = array(); - $commands[] = ajax_command_settings(array('foo' => 42)); - $result = $this->drupalGetAJAX('ajax-test/render', array('query' => array('commands' => $commands))); - // Verify that JavaScript settings are contained (always first). - $this->assertIdentical($result[0]['command'], 'settings', t('drupal_add_js() settings are contained first.')); - // Verify that the custom setting is contained. - $this->assertEqual($result[1]['settings']['foo'], 42, t('Custom setting is output.')); - } - - /** - * Test the various AJAX Commands. - */ - function testAJAXCommands() { - $form_path = 'ajax_forms_test_ajax_commands_form'; - $web_user = $this->drupalCreateUser(array('access content')); - $this->drupalLogin($web_user); - - $edit = array(); - - // Tests the 'after' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'After': Click to put something after the div")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'after' && $command['data'] == 'This will be placed after', "'after' AJAX command issued with correct data"); - - // Tests the 'alert' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Alert': Click to alert")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'alert' && $command['text'] == 'Alert', "'alert' AJAX Command issued with correct text"); - - // Tests the 'append' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'Append': Click to append something")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'append' && $command['data'] == 'Appended text', "'append' AJAX command issued with correct data"); - - // Tests the 'before' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'before': Click to put something before the div")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'before' && $command['data'] == 'Before text', "'before' AJAX command issued with correct data"); - - // Tests the 'changed' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed.")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div', "'changed' AJAX command issued with correct selector"); - - // Tests the 'changed' command using the second argument. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX changed: Click to mark div changed with asterisk.")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'changed' && $command['selector'] == '#changed_div' && $command['asterisk'] == '#changed_div_mark_this', "'changed' AJAX command (with asterisk) issued with correct selector"); - - // Tests the 'css' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("Set the the '#box' div to be blue.")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'css' && $command['selector'] == '#css_div' && $command['argument']['background-color'] == 'blue', "'css' AJAX command issued with correct selector"); - - // Tests the 'data' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX data command: Issue command.")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'data' && $command['name'] == 'testkey' && $command['value'] == 'testvalue', "'data' AJAX command issued with correct key and value"); - - // Tests the 'html' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX html: Replace the HTML in a selector.")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'html' && $command['data'] == 'replacement text', "'html' AJAX command issued with correct data"); - - // Tests the 'insert' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX insert: Let client insert based on #ajax['method'].")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'insert' && $command['method'] == NULL && $command['data'] == 'insert replacement text', "'insert' AJAX command issued with correct data"); - - // Tests the 'prepend' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'prepend': Click to prepend something")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'insert' && $command['method'] == 'prepend' && $command['data'] == 'prepended text', "'prepend' AJAX command issued with correct data"); - - // Tests the 'remove' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'remove': Click to remove text")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'remove' && $command['selector'] == '#remove_text', "'remove' AJAX command issued with correct command and selector"); - - // Tests the 'restripe' command. - $commands = $this->discardSettings($this->drupalPostAJAX($form_path, $edit, array('op' => t("AJAX 'restripe' command")))); - $command = $commands[0]; - $this->assertTrue($command['command'] == 'restripe' && $command['selector'] == '#restripe_table', "'restripe' AJAX command issued with correct selector"); - } -} - -/** - * Test that $form_state['values'] is properly delivered to $ajax['callback']. - */ -class AJAXFormValuesTestCase extends AJAXTestCase { - public static function getInfo() { - return array( - 'name' => 'AJAX command form values', - 'description' => 'Tests that form values are properly delivered to AJAX callbacks.', - 'group' => 'AJAX', - ); - } - - function setUp() { - parent::setUp(); - - $this->web_user = $this->drupalCreateUser(array('access content')); - $this->drupalLogin($this->web_user); - } - - /** - * Create a simple form, then POST to system/ajax to change to it. - */ - function testSimpleAJAXFormValue() { - // Verify form values of a select element. - foreach(array('red', 'green', 'blue') as $item) { - $edit = array( - 'select' => $item, - ); - $commands = $this->discardSettings($this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'select')); - $data_command = $commands[1]; - $this->assertEqual($data_command['value'], $item); - } - - // Verify form values of a checkbox element. - foreach(array(FALSE, TRUE) as $item) { - $edit = array( - 'checkbox' => $item, - ); - $commands = $this->discardSettings($this->drupalPostAJAX('ajax_forms_test_get_form', $edit, 'checkbox')); - $data_command = $commands[1]; - $this->assertEqual((int) $data_command['value'], (int) $item); - } - } -} - -/** - * Tests that AJAX-enabled forms work when multiple instances of the same form are on a page. - */ -class AJAXMultiFormTestCase extends AJAXTestCase { - public static function getInfo() { - return array( - 'name' => 'AJAX multi form', - 'description' => 'Tests that AJAX-enabled forms work when multiple instances of the same form are on a page.', - 'group' => 'AJAX', - ); - } - - function setUp() { - parent::setUp(array('form_test')); - - // Create a multi-valued field for 'page' nodes to use for AJAX testing. - $field_name = 'field_ajax_test'; - $field = array( - 'field_name' => $field_name, - 'type' => 'text', - 'cardinality' => FIELD_CARDINALITY_UNLIMITED, - ); - field_create_field($field); - $instance = array( - 'field_name' => $field_name, - 'entity_type' => 'node', - 'bundle' => 'page', - ); - field_create_instance($instance); - - // Login a user who can create 'page' nodes. - $this->web_user = $this->drupalCreateUser(array('create page content')); - $this->drupalLogin($this->web_user); - } - - /** - * Test that a page with the 'page_node_form' included twice works correctly. - */ - function testMultiForm() { - // HTML IDs for elements within the field are potentially modified with - // each AJAX submission, but these variables are stable and help target the - // desired elements. - $field_name = 'field_ajax_test'; - $field_xpaths = array( - 'page-node-form' => '//form[@id="page-node-form"]//div[contains(@class, "field-name-field-ajax-test")]', - 'page-node-form--2' => '//form[@id="page-node-form--2"]//div[contains(@class, "field-name-field-ajax-test")]', - ); - $button_name = $field_name . '_add_more'; - $button_value = t('Add another item'); - $button_xpath_suffix = '//input[@name="' . $button_name . '"]'; - $field_items_xpath_suffix = '//input[@type="text"]'; - - // Ensure the initial page contains both node forms and the correct number - // of field items and "add more" button for the multi-valued field within - // each form. - $this->drupalGet('form-test/two-instances-of-same-form'); - foreach ($field_xpaths as $form_id => $field_xpath) { - $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == 1, t('Found the correct number of field items on the initial page.')); - $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button on the initial page.')); - } - $this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other'); - - // Submit the "add more" button of each form twice. After each corresponding - // page update, ensure the same as above. To successfully implement - // consecutive AJAX submissions, we need to manage $settings as ajax.js - // does for Drupal.settings. - preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches); - $settings = drupal_json_decode($matches[1]); - foreach ($field_xpaths as $form_id => $field_xpath) { - for ($i=0; $i<2; $i++) { - $button = $this->xpath($field_xpath . $button_xpath_suffix); - $button_id = (string) $button[0]['id']; - $commands = $this->drupalPostAJAX(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_id, $settings['ajax'][$button_id]); - $settings = array_merge_recursive($settings, $commands[0]['settings']); - $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == $i+2, t('Found the correct number of field items after an AJAX submission.')); - $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button after an AJAX submission.')); - $this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other'); - } - } - } -} - -/** - * Miscellaneous AJAX tests using ajax_test module. - */ -class AJAXElementValidation extends AJAXTestCase { - public static function getInfo() { - return array( - 'name' => 'Miscellaneous AJAX tests', - 'description' => 'Various tests of AJAX behavior', - 'group' => 'AJAX', - ); - } - - /** - * Try to post an AJAX change to a form that has a validated element. - * - * The drivertext field is AJAX-enabled. An additional field is not, but - * is set to be a required field. In this test the required field is not - * filled in, and we want to see if the activation of the "drivertext" - * AJAX-enabled field fails due to the required field being empty. - */ - function testAJAXElementValidation() { - $web_user = $this->drupalCreateUser(); - $edit = array('drivertext' => t('some dumb text')); - - // Post with 'drivertext' as the triggering element. - $post_result = $this->drupalPostAJAX('ajax_validation_test', $edit, 'drivertext'); - // Look for a validation failure in the resultant JSON. - $this->assertNoText(t('Error message'), t("No error message in resultant JSON")); - $this->assertText('ajax_forms_test_validation_form_callback invoked', t('The correct callback was invoked')); - } -} - diff --git modules/simpletest/tests/batch.test modules/simpletest/tests/batch.test deleted file mode 100644 index 5c11f72..0000000 --- modules/simpletest/tests/batch.test +++ /dev/null @@ -1,367 +0,0 @@ - 'Batch processing', - 'description' => 'Test batch processing in form and non-form workflow.', - 'group' => 'Batch API', - ); - } - - function setUp() { - parent::setUp('batch_test'); - } - - /** - * Test batches triggered outside of form submission. - */ - function testBatchNoForm() { - // Displaying the page triggers batch 1. - $this->drupalGet('batch_test/no_form'); - $this->assertBatchMessages($this->_resultMessages(1), t('Batch for step 2 performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - } - - /** - * Test batches defined in a form submit handler. - */ - function testBatchForm() { - // Batch 0: no operation. - $edit = array('batch' => 'batch_0'); - $this->drupalPost('batch_test/simple', $edit, 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_0'), t('Batch with no operation performed successfully.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - - // Batch 1: several simple operations. - $edit = array('batch' => 'batch_1'); - $this->drupalPost('batch_test/simple', $edit, 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch with simple operations performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - - // Batch 2: one multistep operation. - $edit = array('batch' => 'batch_2'); - $this->drupalPost('batch_test/simple', $edit, 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_2'), t('Batch with multistep operation performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), t('Execution order was correct.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - - // Batch 3: simple + multistep combined. - $edit = array('batch' => 'batch_3'); - $this->drupalPost('batch_test/simple', $edit, 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_3'), t('Batch with simple and multistep operations performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_3'), t('Execution order was correct.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - - // Batch 4: nested batch. - $edit = array('batch' => 'batch_4'); - $this->drupalPost('batch_test/simple', $edit, 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_4'), t('Nested batch performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_4'), t('Execution order was correct.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - } - - /** - * Test batches defined in a multistep form. - */ - function testBatchFormMultistep() { - $this->drupalGet('batch_test/multistep'); - $this->assertText('step 1', t('Form is displayed in step 1.')); - - // First step triggers batch 1. - $this->drupalPost(NULL, array(), 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch for step 1 performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.')); - $this->assertText('step 2', t('Form is displayed in step 2.')); - - // Second step triggers batch 2. - $this->drupalPost(NULL, array(), 'Submit'); - $this->assertBatchMessages($this->_resultMessages('batch_2'), t('Batch for step 2 performed successfully.')); - $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_2'), t('Execution order was correct.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - } - - /** - * Test batches defined in different submit handlers on the same form. - */ - function testBatchFormMultipleBatches() { - // Batches 1, 2 and 3 are triggered in sequence by different submit - // handlers. Each submit handler modify the submitted 'value'. - $value = rand(0, 255); - $edit = array('value' => $value); - $this->drupalPost('batch_test/chained', $edit, 'Submit'); - // Check that result messages are present and in the correct order. - $this->assertBatchMessages($this->_resultMessages('chained'), t('Batches defined in separate submit handlers performed successfully.')); - // The stack contains execution order of batch callbacks and submit - // hanlders and logging of corresponding $form_state[{values']. - $this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), t('Execution order was correct, and $form_state is correctly persisted.')); - $this->assertText('Redirection successful.', t('Redirection after batch execution is correct.')); - } - - /** - * Test batches defined in a programmatically submitted form. - * - * Same as above, but the form is submitted through drupal_form_execute(). - */ - function testBatchFormProgrammatic() { - // Batches 1, 2 and 3 are triggered in sequence by different submit - // handlers. Each submit handler modify the submitted 'value'. - $value = rand(0, 255); - $this->drupalGet('batch_test/programmatic/' . $value); - // Check that result messages are present and in the correct order. - $this->assertBatchMessages($this->_resultMessages('chained'), t('Batches defined in separate submit handlers performed successfully.')); - // The stack contains execution order of batch callbacks and submit - // hanlders and logging of corresponding $form_state[{values']. - $this->assertEqual(batch_test_stack(), $this->_resultStack('chained', $value), t('Execution order was correct, and $form_state is correctly persisted.')); - $this->assertText('Got out of a programmatic batched form.', t('Page execution continues normally.')); - } - - /** - * Test that drupal_form_submit() can run within a batch operation. - */ - function testDrupalFormSubmitInBatch() { - // Displaying the page triggers a batch that programmatically submits a - // form. - $value = rand(0, 255); - $this->drupalGet('batch_test/nested_programmatic/' . $value); - $this->assertEqual(batch_test_stack(), array('mock form submitted with value = ' . $value), t('drupal_form_submit() ran successfully within a batch operation.')); - } - - /** - * Will trigger a pass if the texts were found in order in the raw content. - * - * @param $texts - * Array of raw strings to look for . - * @param $message - * Message to display. - * @return - * TRUE on pass, FALSE on fail. - */ - function assertBatchMessages($texts, $message) { - $pattern = '|' . implode('.*', $texts) .'|s'; - return $this->assertPattern($pattern, $message); - } - - /** - * Helper function: return expected execution stacks for the test batches. - */ - function _resultStack($id, $value = 0) { - $stack = array(); - switch ($id) { - case 'batch_1': - for ($i = 1; $i <= 10; $i++) { - $stack[] = "op 1 id $i"; - } - break; - - case 'batch_2': - for ($i = 1; $i <= 10; $i++) { - $stack[] = "op 2 id $i"; - } - break; - - case 'batch_3': - for ($i = 1; $i <= 5; $i++) { - $stack[] = "op 1 id $i"; - } - for ($i = 1; $i <= 5; $i++) { - $stack[] = "op 2 id $i"; - } - for ($i = 6; $i <= 10; $i++) { - $stack[] = "op 1 id $i"; - } - for ($i = 6; $i <= 10; $i++) { - $stack[] = "op 2 id $i"; - } - break; - - case 'batch_4': - for ($i = 1; $i <= 5; $i++) { - $stack[] = "op 1 id $i"; - } - $stack[] = 'setting up batch 2'; - for ($i = 6; $i <= 10; $i++) { - $stack[] = "op 1 id $i"; - } - $stack = array_merge($stack, $this->_resultStack('batch_2')); - break; - - case 'chained': - $stack[] = 'submit handler 1'; - $stack[] = 'value = ' . $value; - $stack = array_merge($stack, $this->_resultStack('batch_1')); - $stack[] = 'submit handler 2'; - $stack[] = 'value = ' . ($value + 1); - $stack = array_merge($stack, $this->_resultStack('batch_2')); - $stack[] = 'submit handler 3'; - $stack[] = 'value = ' . ($value + 2); - $stack[] = 'submit handler 4'; - $stack[] = 'value = ' . ($value + 3); - $stack = array_merge($stack, $this->_resultStack('batch_3')); - break; - } - return $stack; - } - - /** - * Helper function: return expected result messages for the test batches. - */ - function _resultMessages($id) { - $messages = array(); - - switch ($id) { - case 'batch_0': - $messages[] = 'results for batch 0
none'; - break; - - case 'batch_1': - $messages[] = 'results for batch 1
op 1: processed 10 elements'; - break; - - case 'batch_2': - $messages[] = 'results for batch 2
op 2: processed 10 elements'; - break; - - case 'batch_3': - $messages[] = 'results for batch 3
op 1: processed 10 elements
op 2: processed 10 elements'; - break; - - case 'batch_4': - $messages[] = 'results for batch 4
op 1: processed 10 elements'; - $messages = array_merge($messages, $this->_resultMessages('batch_2')); - break; - - case 'chained': - $messages = array_merge($messages, $this->_resultMessages('batch_1')); - $messages = array_merge($messages, $this->_resultMessages('batch_2')); - $messages = array_merge($messages, $this->_resultMessages('batch_3')); - break; - } - return $messages; - } -} - -/** - * Tests for the Batch API Progress page. - */ -class BatchPageTestCase extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Batch progress page', - 'description' => 'Test the content of the progress page.', - 'group' => 'Batch API', - ); - } - - function setUp() { - parent::setUp('batch_test'); - } - - /** - * Tests that the batch API progress page uses the correct theme. - */ - function testBatchProgressPageTheme() { - // Make sure that the page which starts the batch (an administrative page) - // is using a different theme than would normally be used by the batch API. - variable_set('theme_default', 'garland'); - variable_set('admin_theme', 'seven'); - // Visit an administrative page that runs a test batch, and check that the - // theme that was used during batch execution (which the batch callback - // function saved as a variable) matches the theme used on the - // administrative page. - $this->drupalGet('admin/batch_test/test_theme'); - // The stack should contain the name of the the used on the progress page. - $this->assertEqual(batch_test_stack(), array('seven'), t('A progressive batch correctly uses the theme of the page that started the batch.')); - } -} - -/** - * Tests the function _batch_api_percentage() to make sure that the rounding - * works properly in all cases. - */ -class BatchPercentagesUnitTestCase extends DrupalUnitTestCase { - protected $testCases = array(); - - public static function getInfo() { - return array( - 'name' => 'Batch percentages', - 'description' => 'Unit tests of progress percentage rounding.', - 'group' => 'Batch API', - ); - } - - function setUp() { - // Set up an array of test cases, where the expected values are the keys, - // and the values are arrays with the keys 'total' and 'current', - // corresponding with the function parameters of _batch_api_percentage(). - $this->testCases = array( - // 1/2 is 50%. - '50' => array('total' => 2, 'current' => 1), - // Though we should never encounter a case where the current set is set - // 0, if we did, we should get 0%. - '0' => array('total' => 3, 'current' => 0), - // 1/3 is closer to 33% than to 34%. - '33' => array('total' => 3, 'current' => 1), - // 2/3 is closer to 67% than to 66%. - '67' => array('total' => 3, 'current' => 2), - // A full 3/3 should equal 100%. - '100' => array('total' => 3, 'current' => 3), - // 1/199 should round up to 1%. - '1' => array('total' => 199, 'current' => 1), - // 198/199 should round down to 99%. - '99' => array('total' => 199, 'current' => 198), - // 199/200 would have rounded up to 100%, which would give the false - // impression of being finished, so we add another digit and should get - // 99.5%. - '99.5' => array('total' => 200, 'current' => 199), - // The same logic holds for 1/200: we should get 0.5%. - '0.5' => array('total' => 200, 'current' => 1), - // Numbers that come out evenly, such as 50/200, should be forced to have - // extra digits for consistancy. - '25.0' => array('total' => 200, 'current' => 50), - // Regardless of number of digits we're using, 100% should always just be - // 100%. - '100' => array('total' => 200, 'current' => 200), - // 1998/1999 should similarly round down to 99.9%. - '99.9' => array('total' => 1999, 'current' => 1998), - // 1999/2000 should add another digit and go to 99.95%. - '99.95' => array('total' => 2000, 'current' => 1999), - // 19999/20000 should add yet another digit and go to 99.995%. - '99.995' => array('total' => 20000, 'current' => 19999), - ); - - parent::setUp(); - } - - /** - * Test the _batch_api_percentage() function. - */ - function testBatchPercentages() { - require_once DRUPAL_ROOT . '/includes/batch.inc'; - foreach ($this->testCases as $expected_result => $arguments) { - // PHP sometimes casts numeric strings that are array keys to integers, - // cast them back here. - $expected_result = (string) $expected_result; - $total = $arguments['total']; - $current = $arguments['current']; - $actual_result = _batch_api_percentage($total, $current); - if ($actual_result === $expected_result) { - $this->pass(t('Expected the batch api percentage at the state @numerator/@denominator to be @expected%, and got @actual%.', array('@numerator' => $current, '@denominator' => $total, '@expected' => $expected_result, '@actual' => $actual_result))); - } - else { - $this->fail(t('Expected the batch api percentage at the state @numerator/@denominator to be @expected%, but got @actual%.', array('@numerator' => $current, '@denominator' => $total, '@expected' => $expected_result, '@actual' => $actual_result))); - } - } - } -} diff --git modules/simpletest/tests/bootstrap.test modules/simpletest/tests/bootstrap.test deleted file mode 100644 index 95d7244..0000000 --- modules/simpletest/tests/bootstrap.test +++ /dev/null @@ -1,428 +0,0 @@ - 'IP address and HTTP_HOST test', - 'description' => 'Get the IP address from the current visitor from the server variables, check hostname validation.', - 'group' => 'Bootstrap' - ); - } - - function setUp() { - $this->oldserver = $_SERVER; - - $this->remote_ip = '127.0.0.1'; - $this->proxy_ip = '127.0.0.2'; - $this->proxy2_ip = '127.0.0.3'; - $this->forwarded_ip = '127.0.0.4'; - $this->cluster_ip = '127.0.0.5'; - $this->untrusted_ip = '0.0.0.0'; - - drupal_static_reset('ip_address'); - - $_SERVER['REMOTE_ADDR'] = $this->remote_ip; - unset($_SERVER['HTTP_X_FORWARDED_FOR']); - unset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']); - - parent::setUp(); - } - - function tearDown() { - $_SERVER = $this->oldserver; - drupal_static_reset('ip_address'); - parent::tearDown(); - } - - /** - * test IP Address and hostname - */ - function testIPAddressHost() { - // Test the normal IP address. - $this->assertTrue( - ip_address() == $this->remote_ip, - t('Got remote IP address.') - ); - - // Proxy forwarding on but no proxy addresses defined. - variable_set('reverse_proxy', 1); - $this->assertTrue( - ip_address() == $this->remote_ip, - t('Proxy forwarding without trusted proxies got remote IP address.') - ); - - // Proxy forwarding on and proxy address not trusted. - variable_set('reverse_proxy_addresses', array($this->proxy_ip, $this->proxy2_ip)); - drupal_static_reset('ip_address'); - $_SERVER['REMOTE_ADDR'] = $this->untrusted_ip; - $this->assertTrue( - ip_address() == $this->untrusted_ip, - t('Proxy forwarding with untrusted proxy got remote IP address.') - ); - - // Proxy forwarding on and proxy address trusted. - $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; - $_SERVER['HTTP_X_FORWARDED_FOR'] = $this->forwarded_ip; - drupal_static_reset('ip_address'); - $this->assertTrue( - ip_address() == $this->forwarded_ip, - t('Proxy forwarding with trusted proxy got forwarded IP address.') - ); - - // Multi-tier architecture with comma separated values in header. - $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; - $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip)); - drupal_static_reset('ip_address'); - $this->assertTrue( - ip_address() == $this->forwarded_ip, - t('Proxy forwarding with trusted 2-tier proxy got forwarded IP address.') - ); - - // Custom client-IP header. - variable_set('reverse_proxy_header', 'HTTP_X_CLUSTER_CLIENT_IP'); - $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'] = $this->cluster_ip; - drupal_static_reset('ip_address'); - $this->assertTrue( - ip_address() == $this->cluster_ip, - t('Cluster environment got cluster client IP.') - ); - - // Verifies that drupal_valid_http_host() prevents invalid characters. - $this->assertFalse(drupal_valid_http_host('security/.drupal.org:80'), t('HTTP_HOST with / is invalid')); - $this->assertFalse(drupal_valid_http_host('security\\.drupal.org:80'), t('HTTP_HOST with \\ is invalid')); - $this->assertFalse(drupal_valid_http_host('security<.drupal.org:80'), t('HTTP_HOST with < is invalid')); - $this->assertFalse(drupal_valid_http_host('security..drupal.org:80'), t('HTTP_HOST with .. is invalid')); - // IPv6 loopback address - $this->assertTrue(drupal_valid_http_host('[::1]:80'), t('HTTP_HOST containing IPv6 loopback is valid')); - } -} - -class BootstrapPageCacheTestCase extends DrupalWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Page cache test', - 'description' => 'Enable the page cache and test it with various HTTP requests.', - 'group' => 'Bootstrap' - ); - } - - function setUp() { - parent::setUp('system_test'); - } - - /** - * Test support for requests containing If-Modified-Since and If-None-Match headers. - */ - function testConditionalRequests() { - variable_set('cache', 1); - - // Fill the cache. - $this->drupalGet(''); - - $this->drupalHead(''); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); - $etag = $this->drupalGetHeader('ETag'); - $last_modified = $this->drupalGetHeader('Last-Modified'); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag)); - $this->assertResponse(304, t('Conditional request returned 304 Not Modified.')); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC822, strtotime($last_modified)), 'If-None-Match: ' . $etag)); - $this->assertResponse(304, t('Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.')); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC850, strtotime($last_modified)), 'If-None-Match: ' . $etag)); - $this->assertResponse(304, t('Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.')); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified)); - $this->assertResponse(200, t('Conditional request without If-None-Match returned 200 OK.')); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC1123, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag)); - $this->assertResponse(200, t('Conditional request with new a If-Modified-Since date newer than Last-Modified returned 200 OK.')); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); - - $user = $this->drupalCreateUser(); - $this->drupalLogin($user); - $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag)); - $this->assertResponse(200, t('Conditional request returned 200 OK for authenticated user.')); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Absense of Page was not cached.')); - } - - /** - * Test cache headers. - */ - function testPageCache() { - variable_set('cache', 1); - - // Fill the cache. - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.')); - $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', t('Vary header was sent.')); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', t('Cache-Control header was sent.')); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.')); - $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.')); - - // Check cache. - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); - $this->assertEqual($this->drupalGetHeader('Vary'), 'Cookie,Accept-Encoding', t('Vary: Cookie header was sent.')); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'public, max-age=0', t('Cache-Control header was sent.')); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.')); - $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.')); - - // Check replacing default headers. - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT'))); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Fri, 19 Nov 2008 05:00:00 GMT', t('Default header was replaced.')); - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Vary', 'value' => 'User-Agent'))); - $this->assertEqual($this->drupalGetHeader('Vary'), 'User-Agent,Accept-Encoding', t('Default header was replaced.')); - - // Check that authenticated users bypass the cache. - $user = $this->drupalCreateUser(); - $this->drupalLogin($user); - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), t('Caching was bypassed.')); - $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, t('Vary: Cookie header was not sent.')); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'no-cache, must-revalidate, post-check=0, pre-check=0', t('Cache-Control header was sent.')); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', t('Expires header was sent.')); - $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', t('Custom header was sent.')); - - } - - /** - * Test page compression. - * - * The test should pass even if zlib.output_compression is enabled in php.ini, - * .htaccess or similar, or if compression is done outside PHP, e.g. by the - * mod_deflate Apache module. - */ - function testPageCompression() { - variable_set('cache', 1); - - // Fill the cache and verify that output is compressed. - $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate')); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Page was not cached.')); - $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8))); - $this->assertRaw('', t('Page was gzip compressed.')); - - // Verify that cached output is compressed. - $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate')); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); - $this->assertEqual($this->drupalGetHeader('Content-Encoding'), 'gzip', t('A Content-Encoding header was sent.')); - $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8))); - $this->assertRaw('', t('Page was gzip compressed.')); - - // Verify that a client without compression support gets an uncompressed page. - $this->drupalGet(''); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Page was cached.')); - $this->assertFalse($this->drupalGetHeader('Content-Encoding'), t('A Content-Encoding header was not sent.')); - $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), t('Site title matches.')); - $this->assertRaw('', t('Page was not compressed.')); - } -} - -class BootstrapVariableTestCase extends DrupalWebTestCase { - - function setUp() { - parent::setUp('system_test'); - } - - public static function getInfo() { - return array( - 'name' => 'Variable test', - 'description' => 'Make sure the variable system functions correctly.', - 'group' => 'Bootstrap' - ); - } - - /** - * testVariable - */ - function testVariable() { - // Setting and retrieving values. - $variable = $this->randomName(); - variable_set('simpletest_bootstrap_variable_test', $variable); - $this->assertIdentical($variable, variable_get('simpletest_bootstrap_variable_test'), t('Setting and retrieving values')); - - // Make sure the variable persists across multiple requests. - $this->drupalGet('system-test/variable-get'); - $this->assertText($variable, t('Variable persists across multiple requests')); - - // Deleting variables. - $default_value = $this->randomName(); - variable_del('simpletest_bootstrap_variable_test'); - $variable = variable_get('simpletest_bootstrap_variable_test', $default_value); - $this->assertIdentical($variable, $default_value, t('Deleting variables')); - } - - /** - * Makes sure that the default variable parameter is passed through okay. - */ - function testVariableDefaults() { - // Tests passing nothing through to the default. - $this->assertIdentical(NULL, variable_get('simpletest_bootstrap_variable_test'), t('Variables are correctly defaulting to NULL.')); - - // Tests passing 5 to the default parameter. - $this->assertIdentical(5, variable_get('simpletest_bootstrap_variable_test', 5), t('The default variable parameter is passed through correctly.')); - } - -} - -/** - * Test hook_boot() and hook_exit(). - */ -class HookBootExitTestCase extends DrupalWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Boot and exit hook invocation', - 'description' => 'Test that hook_boot() and hook_exit() are called correctly.', - 'group' => 'Bootstrap', - ); - } - - function setUp() { - parent::setUp('system_test', 'dblog'); - } - - /** - * Test calling of hook_boot() and hook_exit(). - */ - function testHookBootExit() { - // Test with cache disabled. Boot and exit should always fire. - variable_set('cache', 0); - $this->drupalGet(''); - $calls = 1; - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with disabled cache.')); - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with disabled cache.')); - - // Test with normal cache. Boot and exit should be called. - variable_set('cache', 1); - $this->drupalGet(''); - $calls++; - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with normal cache.')); - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with normal cache.')); - - // Boot and exit should not fire since the page is cached. - variable_set('page_cache_invoke_hooks', FALSE); - $this->assertTrue(cache_get(url('', array('absolute' => TRUE)), 'cache_page'), t('Page has been cached.')); - $this->drupalGet(''); - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot not called with agressive cache and a cached page.')); - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit not called with agressive cache and a cached page.')); - - // Test with page cache cleared, boot and exit should be called. - $this->assertTrue(db_delete('cache_page')->execute(), t('Page cache cleared.')); - $this->drupalGet(''); - $calls++; - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_boot'))->fetchField(), $calls, t('hook_boot called with agressive cache and no cached page.')); - $this->assertEqual(db_query('SELECT COUNT(*) FROM {watchdog} WHERE type = :type AND message = :message', array(':type' => 'system_test', ':message' => 'hook_exit'))->fetchField(), $calls, t('hook_exit called with agressive cache and no cached page.')); - } -} - -/** - * Test drupal_get_filename()'s availability. - */ -class BootstrapGetFilenameTestCase extends DrupalUnitTestCase { - - public static function getInfo() { - return array( - 'name' => 'Get filename test', - 'description' => 'Test that drupal_get_filename() works correctly when the file is not found in the database.', - 'group' => 'Bootstrap', - ); - } - - /** - * Test that drupal_get_filename() works correctly when the file is not found in the database. - */ - function testDrupalGetFilename() { - // Reset the static cache so we can test the "db is not active" code of - // drupal_get_filename(). - drupal_static_reset('drupal_get_filename'); - - // Retrieving the location of a module. - $this->assertIdentical(drupal_get_filename('module', 'php'), 'modules/php/php.module', t('Retrieve module location.')); - - // Retrieving the location of a theme. - $this->assertIdentical(drupal_get_filename('theme', 'stark'), 'themes/stark/stark.info', t('Retrieve theme location.')); - - // Retrieving the location of a theme engine. - $this->assertIdentical(drupal_get_filename('theme_engine', 'phptemplate'), 'themes/engines/phptemplate/phptemplate.engine', t('Retrieve theme engine location.')); - - // Retrieving a file that is definitely not stored in the database. - $this->assertIdentical(drupal_get_filename('profile', 'standard'), 'profiles/standard/standard.profile', t('Retrieve install profile location.')); - } -} - -class BootstrapTimerTestCase extends DrupalUnitTestCase { - - public static function getInfo() { - return array( - 'name' => 'Timer test', - 'description' => 'Test that timer_read() works both when a timer is running and when a timer is stopped.', - 'group' => 'Bootstrap', - ); - } - - /** - * Test timer_read() to ensure it properly accumulates time when the timer - * started and stopped multiple times. - * @return - */ - function testTimer() { - timer_start('test'); - sleep(1); - $this->assertTrue(timer_read('test') >= 1000, t('Timer measured 1 second of sleeping while running.')); - sleep(1); - timer_stop('test'); - $this->assertTrue(timer_read('test') >= 2000, t('Timer measured 2 seconds of sleeping after being stopped.')); - timer_start('test'); - sleep(1); - $this->assertTrue(timer_read('test') >= 3000, t('Timer measured 3 seconds of sleeping after being restarted.')); - sleep(1); - $timer = timer_stop('test'); - $this->assertTrue(timer_read('test') >= 4000, t('Timer measured 4 seconds of sleeping after being stopped for a second time.')); - $this->assertEqual($timer['count'], 2, t('Timer counted 2 instances of being started.')); - } -} - -/** - * Test that resetting static variables works. - */ -class BootstrapResettableStaticTestCase extends DrupalUnitTestCase { - - public static function getInfo() { - return array( - 'name' => 'Resettable static variables test', - 'description' => 'Test that drupal_static() and drupal_static_reset() work.', - 'group' => 'Bootstrap', - ); - } - - /** - * Test that a variable reference returned by drupal_static() gets reset when - * drupal_static_reset() is called. - */ - function testDrupalStatic() { - $name = __CLASS__ . '_' . __METHOD__; - $var = &drupal_static($name, 'foo'); - $this->assertEqual($var, 'foo', t('Variable returned by drupal_static() was set to its default.')); - - // Call the specific reset and the global reset each twice to ensure that - // multiple resets can be issued without odd side effects. - $var = 'bar'; - drupal_static_reset($name); - $this->assertEqual($var, 'foo', t('Variable was reset after first invocation of name-specific reset.')); - $var = 'bar'; - drupal_static_reset($name); - $this->assertEqual($var, 'foo', t('Variable was reset after second invocation of name-specific reset.')); - $var = 'bar'; - drupal_static_reset(); - $this->assertEqual($var, 'foo', t('Variable was reset after first invocation of global reset.')); - $var = 'bar'; - drupal_static_reset(); - $this->assertEqual($var, 'foo', t('Variable was reset after second invocation of global reset.')); - } -} diff --git modules/simpletest/tests/cache.test modules/simpletest/tests/cache.test deleted file mode 100644 index d5a9c5f..0000000 --- modules/simpletest/tests/cache.test +++ /dev/null @@ -1,351 +0,0 @@ -default_bin; - } - - $cache = cache_get($cid, $bin); - - return isset($cache->data) && $cache->data == $var; - } - - /** - * Assert or a cache entry exists. - * - * @param $message - * Message to display. - * @param $var - * The variable the cache should contain. - * @param $cid - * The cache id. - * @param $bin - * The bin the cache item was stored in. - */ - protected function assertCacheExists($message, $var = NULL, $cid = NULL, $bin = NULL) { - if ($bin == NULL) { - $bin = $this->default_bin; - } - if ($cid == NULL) { - $cid = $this->default_cid; - } - if ($var == NULL) { - $var = $this->default_value; - } - - $this->assertTrue($this->checkCacheExists($cid, $var, $bin), $message); - } - - /** - * Assert or a cache entry has been removed. - * - * @param $message - * Message to display. - * @param $cid - * The cache id. - * @param $bin - * The bin the cache item was stored in. - */ - function assertCacheRemoved($message, $cid = NULL, $bin = NULL) { - if ($bin == NULL) { - $bin = $this->default_bin; - } - if ($cid == NULL) { - $cid = $this->default_cid; - } - - $cache = cache_get($cid, $bin); - $this->assertFalse($cache, $message); - } - - /** - * Perform the general wipe. - * @param $bin - * The bin to perform the wipe on. - */ - protected function generalWipe($bin = NULL) { - if ($bin == NULL) { - $bin = $this->default_bin; - } - - cache_clear_all(NULL, $bin); - } - - /** - * Setup the lifetime settings for caching. - * - * @param $time - * The time in seconds the cache should minimal live. - */ - protected function setupLifetime($time) { - variable_set('cache_lifetime', $time); - variable_set('cache_flush', 0); - } -} - -class CacheSavingCase extends CacheTestCase { - public static function getInfo() { - return array( - 'name' => 'Cache saving test', - 'description' => 'Check our variables are saved and restored the right way.', - 'group' => 'Cache' - ); - } - - /** - * Test the saving and restoring of a string. - */ - function testString() { - $this->checkVariable($this->randomName(100)); - } - - /** - * Test the saving and restoring of an integer. - */ - function testInteger() { - $this->checkVariable(100); - } - - /** - * Test the saving and restoring of a double. - */ - function testDouble() { - $this->checkVariable(1.29); - } - - /** - * Test the saving and restoring of an array. - */ - function testArray() { - $this->checkVariable(array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6'))); - } - - /** - * Test the saving and restoring of an object. - */ - function testObject() { - $test_object = new stdClass(); - $test_object->test1 = $this->randomName(100); - $test_object->test2 = 100; - $test_object->test3 = array('drupal1', 'drupal2' => 'drupal3', 'drupal4' => array('drupal5', 'drupal6')); - - cache_set('test_object', $test_object, 'cache'); - $cache = cache_get('test_object', 'cache'); - $this->assertTrue(isset($cache->data) && $cache->data == $test_object, t('Object is saved and restored properly.')); - } - - /* - * Check or a variable is stored and restored properly. - **/ - function checkVariable($var) { - cache_set('test_var', $var, 'cache'); - $cache = cache_get('test_var', 'cache'); - $this->assertTrue(isset($cache->data) && $cache->data === $var, t('@type is saved and restored properly.', array('@type' => ucfirst(gettype($var))))); - } -} - -/** - * Test cache_get_multiple(). - */ -class CacheGetMultipleUnitTest extends CacheTestCase { - - public static function getInfo() { - return array( - 'name' => 'Fetching multiple cache items', - 'description' => 'Confirm that multiple records are fetched correctly.', - 'group' => 'Cache', - ); - } - - function setUp() { - $this->default_bin = 'cache_page'; - parent::setUp(); - } - - /** - * Test cache_get_multiple(). - */ - function testCacheMultiple() { - $item1 = $this->randomName(10); - $item2 = $this->randomName(10); - cache_set('item1', $item1, $this->default_bin); - cache_set('item2', $item2, $this->default_bin); - $this->assertTrue($this->checkCacheExists('item1', $item1), t('Item 1 is cached.')); - $this->assertTrue($this->checkCacheExists('item2', $item2), t('Item 2 is cached.')); - - // Fetch both records from the database with cache_get_multiple(). - $item_ids = array('item1', 'item2'); - $items = cache_get_multiple($item_ids, $this->default_bin); - $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.')); - $this->assertEqual($items['item2']->data, $item2, t('Item was returned from cache successfully.')); - - // Remove one item from the cache. - cache_clear_all('item2', $this->default_bin); - - // Confirm that only one item is returned by cache_get_multiple(). - $item_ids = array('item1', 'item2'); - $items = cache_get_multiple($item_ids, $this->default_bin); - $this->assertEqual($items['item1']->data, $item1, t('Item was returned from cache successfully.')); - $this->assertFalse(isset($items['item2']), t('Item was not returned from the cache.')); - $this->assertTrue(count($items) == 1, t('Only valid cache entries returned.')); - } -} - -/** - * Test cache clearing methods. - */ -class CacheClearCase extends CacheTestCase { - public static function getInfo() { - return array( - 'name' => 'Cache clear test', - 'description' => 'Check our clearing is done the proper way.', - 'group' => 'Cache' - ); - } - - function setUp() { - $this->default_bin = 'cache_page'; - $this->default_value = $this->randomName(10); - - parent::setUp(); - } - - /** - * Test clearing using a cid. - */ - function testClearCid() { - cache_set('test_cid_clear', $this->default_value, $this->default_bin); - - $this->assertCacheExists(t('Cache was set for clearing cid.'), $this->default_value, 'test_cid_clear'); - cache_clear_all('test_cid_clear', $this->default_bin); - - $this->assertCacheRemoved(t('Cache was removed after clearing cid.'), 'test_cid_clear'); - - cache_set('test_cid_clear1', $this->default_value, $this->default_bin); - cache_set('test_cid_clear2', $this->default_value, $this->default_bin); - $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) - && $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two caches were created for checking cid "*" with wildcard false.')); - cache_clear_all('*', $this->default_bin); - $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) - && $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two caches still exists after clearing cid "*" with wildcard false.')); - } - - /** - * Test clearing using wildcard. - */ - function testClearWildcard() { - cache_set('test_cid_clear1', $this->default_value, $this->default_bin); - cache_set('test_cid_clear2', $this->default_value, $this->default_bin); - $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) - && $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two caches were created for checking cid "*" with wildcard true.')); - cache_clear_all('*', $this->default_bin, TRUE); - $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) - || $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two caches removed after clearing cid "*" with wildcard true.')); - - cache_set('test_cid_clear1', $this->default_value, $this->default_bin); - cache_set('test_cid_clear2', $this->default_value, $this->default_bin); - $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) - && $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two caches were created for checking cid substring with wildcard true.')); - cache_clear_all('test_', $this->default_bin, TRUE); - $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) - || $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two caches removed after clearing cid substring with wildcard true.')); - } - - /** - * Test clearing using an array. - */ - function testClearArray() { - // Create three cache entries. - cache_set('test_cid_clear1', $this->default_value, $this->default_bin); - cache_set('test_cid_clear2', $this->default_value, $this->default_bin); - cache_set('test_cid_clear3', $this->default_value, $this->default_bin); - $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) - && $this->checkCacheExists('test_cid_clear2', $this->default_value) - && $this->checkCacheExists('test_cid_clear3', $this->default_value), - t('Three cache entries were created.')); - - // Clear two entries using an array. - cache_clear_all(array('test_cid_clear1', 'test_cid_clear2'), $this->default_bin); - $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) - || $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two cache entries removed after clearing with an array.')); - - $this->assertTrue($this->checkCacheExists('test_cid_clear3', $this->default_value), - t('Entry was not cleared from the cache')); - - // Set the cache clear threshold to 2 to confirm that the full bin is cleared - // when the threshold is exceeded. - variable_set('cache_clear_threshold', 2); - cache_set('test_cid_clear1', $this->default_value, $this->default_bin); - cache_set('test_cid_clear2', $this->default_value, $this->default_bin); - $this->assertTrue($this->checkCacheExists('test_cid_clear1', $this->default_value) - && $this->checkCacheExists('test_cid_clear2', $this->default_value), - t('Two cache entries were created.')); - cache_clear_all(array('test_cid_clear1', 'test_cid_clear2', 'test_cid_clear3'), $this->default_bin); - $this->assertFalse($this->checkCacheExists('test_cid_clear1', $this->default_value) - || $this->checkCacheExists('test_cid_clear2', $this->default_value) - || $this->checkCacheExists('test_cid_clear3', $this->default_value), - t('All cache entries removed when the array exceeded the cache clear threshold.')); - } -} - -/** - * Test cache_is_empty() function. - */ -class CacheIsEmptyCase extends CacheTestCase { - public static function getInfo() { - return array( - 'name' => 'Cache emptiness test', - 'description' => 'Check if a cache bin is empty after performing clear operations.', - 'group' => 'Cache' - ); - } - - function setUp() { - $this->default_bin = 'cache_page'; - $this->default_value = $this->randomName(10); - - parent::setUp(); - } - - /** - * Test clearing using a cid. - */ - function testIsEmpty() { - // Clear the cache bin. - cache_clear_all('*', $this->default_bin); - $this->assertTrue(cache_is_empty($this->default_bin), t('The cache bin is empty')); - // Add some data to the cache bin. - cache_set($this->default_cid, $this->default_value, $this->default_bin); - $this->assertCacheExists(t('Cache was set.'), $this->default_value, $this->default_cid); - $this->assertFalse(cache_is_empty($this->default_bin), t('The cache bin is not empty')); - // Remove the cached data. - cache_clear_all($this->default_cid, $this->default_bin); - $this->assertCacheRemoved(t('Cache was removed.'), $this->default_cid); - $this->assertTrue(cache_is_empty($this->default_bin), t('The cache bin is empty')); - } -} diff --git modules/simpletest/tests/common.test modules/simpletest/tests/common.test deleted file mode 100644 index e81a950..0000000 --- modules/simpletest/tests/common.test +++ /dev/null @@ -1,1990 +0,0 @@ - 'drupal_alter() tests', - 'description' => 'Confirm that alteration of arguments passed to drupal_alter() works correctly.', - 'group' => 'System', - ); - } - - function setUp() { - parent::setUp('common_test'); - } - - function testDrupalAlter() { - // This test depends on Garland, so make sure that it is always the current - // active theme. - global $theme, $base_theme_info; - $theme = 'garland'; - $base_theme_info = array(); - - $array = array('foo' => 'bar'); - $entity = new stdClass(); - $entity->foo = 'bar'; - - // Verify alteration of a single argument. - $array_copy = $array; - $array_expected = array('foo' => 'Drupal theme'); - drupal_alter('drupal_alter', $array_copy); - $this->assertEqual($array_copy, $array_expected, t('Single array was altered.')); - - $entity_copy = clone $entity; - $entity_expected = clone $entity; - $entity_expected->foo = 'Drupal theme'; - drupal_alter('drupal_alter', $entity_copy); - $this->assertEqual($entity_copy, $entity_expected, t('Single object was altered.')); - - // Verify alteration of multiple arguments. - $array_copy = $array; - $array_expected = array('foo' => 'Drupal theme'); - $entity_copy = clone $entity; - $entity_expected = clone $entity; - $entity_expected->foo = 'Drupal theme'; - $array2_copy = $array; - $array2_expected = array('foo' => 'Drupal theme'); - drupal_alter('drupal_alter', $array_copy, $entity_copy, $array2_copy); - $this->assertEqual($array_copy, $array_expected, t('First argument to drupal_alter() was altered.')); - $this->assertEqual($entity_copy, $entity_expected, t('Second argument to drupal_alter() was altered.')); - $this->assertEqual($array2_copy, $array2_expected, t('Third argument to drupal_alter() was altered.')); - } -} - -/** - * Tests for URL generation functions. - * - * url() calls module_implements(), which may issue a db query, which requires - * inheriting from a web test case rather than a unit test case. - */ -class CommonURLUnitTest extends DrupalWebTestCase { - public static function getInfo() { - return array( - 'name' => 'URL generation tests', - 'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.', - 'group' => 'System', - ); - } - - /** - * Confirm that invalid text given as $path is filtered. - */ - function testLXSS() { - $text = $this->randomName(); - $path = ""; - $link = l($text, $path); - $sanitized_path = check_url(url($path)); - $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path))); - } - - /* - * Tests for active class in l() function. - */ - function testLActiveClass() { - $link = l($this->randomName(), $_GET['q']); - $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); - } - - /** - * Tests for custom class in l() function. - */ - function testLCustomClass() { - $class = $this->randomName(); - $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); - $this->assertTrue($this->hasClass($link, $class), t('Custom class @class is present on link when requested', array('@class' => $class))); - $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); - } - - private function hasClass($link, $class) { - return preg_match('|class="([^\"\s]+\s+)*' . $class . '|', $link); - } - - /** - * Test drupal_get_query_parameters(). - */ - function testDrupalGetQueryParameters() { - $original = array( - 'a' => 1, - 'b' => array( - 'd' => 4, - 'e' => array( - 'f' => 5, - ), - ), - 'c' => 3, - 'q' => 'foo/bar', - ); - - // Default arguments. - $result = $_GET; - unset($result['q']); - $this->assertEqual(drupal_get_query_parameters(), $result, t("\$_GET['q'] was removed.")); - - // Default exclusion. - $result = $original; - unset($result['q']); - $this->assertEqual(drupal_get_query_parameters($original), $result, t("'q' was removed.")); - - // First-level exclusion. - $result = $original; - unset($result['b']); - $this->assertEqual(drupal_get_query_parameters($original, array('b')), $result, t("'b' was removed.")); - - // Second-level exclusion. - $result = $original; - unset($result['b']['d']); - $this->assertEqual(drupal_get_query_parameters($original, array('b[d]')), $result, t("'b[d]' was removed.")); - - // Third-level exclusion. - $result = $original; - unset($result['b']['e']['f']); - $this->assertEqual(drupal_get_query_parameters($original, array('b[e][f]')), $result, t("'b[e][f]' was removed.")); - - // Multiple exclusions. - $result = $original; - unset($result['a'], $result['b']['e'], $result['c']); - $this->assertEqual(drupal_get_query_parameters($original, array('a', 'b[e]', 'c')), $result, t("'a', 'b[e]', 'c' were removed.")); - } - - /** - * Test drupal_http_build_query(). - */ - function testDrupalHttpBuildQuery() { - $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', t('Value was properly encoded.')); - $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', t('Key was properly encoded.')); - $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', t('Multiple values were properly concatenated.')); - $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', t('Nested array was properly encoded.')); - } - - /** - * Test drupal_parse_url(). - */ - function testDrupalParseUrl() { - // Relative URL. - $url = 'foo/bar?foo=bar&bar=baz&baz#foo'; - $result = array( - 'path' => 'foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); - - // Relative URL that is known to confuse parse_url(). - $url = 'foo/bar:1'; - $result = array( - 'path' => 'foo/bar:1', - 'query' => array(), - 'fragment' => '', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); - - // Absolute URL. - $url = '/foo/bar?foo=bar&bar=baz&baz#foo'; - $result = array( - 'path' => '/foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL parsed correctly.')); - - // External URL testing. - $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; - - // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. - $this->assertTrue(url_is_external($url), t('Correctly identified an external URL.')); - - // Test the parsing of absolute URLs. - $result = array( - 'path' => 'http://drupal.org/foo/bar', - 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), - 'fragment' => 'foo', - ); - $this->assertEqual(drupal_parse_url($url), $result, t('External URL parsed correctly.')); - - // Verify proper parsing of URLs when clean URLs are disabled. - $result = array( - 'path' => 'foo/bar', - 'query' => array('bar' => 'baz'), - 'fragment' => 'foo', - ); - // Non-clean URLs #1: Absolute URL generated by url(). - $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL with clean URLs disabled parsed correctly.')); - - // Non-clean URLs #2: Relative URL generated by url(). - $url = '?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL with clean URLs disabled parsed correctly.')); - - // Non-clean URLs #3: URL generated by url() on non-Apache webserver. - $url = 'index.php?q=foo/bar&bar=baz#foo'; - $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.')); - - // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. - $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); - $this->assertFalse(valid_url($parts['path'], TRUE), t('drupal_parse_url() correctly parsed a forged URL.')); - } - - /** - * Test url() with/without query, with/without fragment, absolute on/off and - * assert all that works when clean URLs are on and off. - */ - function testUrl() { - global $base_url; - - foreach (array(FALSE, TRUE) as $absolute) { - // Get the expected start of the path string. - $base = $absolute ? $base_url . '/' : base_path(); - $absolute_string = $absolute ? 'absolute' : NULL; - - // Disable Clean URLs. - $GLOBALS['conf']['clean_url'] = 0; - - $url = $base . '?q=node/123'; - $result = url('node/123', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123#foo'; - $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo=bar&bar=baz'; - $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . '?q=node/123&foo#bar'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base; - $result = url('', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - // Enable Clean URLs. - $GLOBALS['conf']['clean_url'] = 1; - - $url = $base . 'node/123'; - $result = url('node/123', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123#foo'; - $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo'; - $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo=bar&bar=baz'; - $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base . 'node/123?foo#bar'; - $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - - $url = $base; - $result = url('', array('absolute' => $absolute)); - $this->assertEqual($url, $result, "$url == $result"); - } - } - - /** - * Test external URL handling. - */ - function testExternalUrls() { - $test_url = 'http://drupal.org/'; - - // Verify external URL can contain a fragment. - $url = $test_url . '#drupal'; - $result = url($url); - $this->assertEqual($url, $result, t('External URL with fragment works without a fragment in $options.')); - - // Verify fragment can be overidden in an external URL. - $url = $test_url . '#drupal'; - $fragment = $this->randomName(10); - $result = url($url, array('fragment' => $fragment)); - $this->assertEqual($test_url . '#' . $fragment, $result, t('External URL fragment is overidden with a custom fragment in $options.')); - - // Verify external URL can contain a query string. - $url = $test_url . '?drupal=awesome'; - $result = url($url); - $this->assertEqual($url, $result, t('External URL with query string works without a query string in $options.')); - - // Verify external URL can be extended with a query string. - $url = $test_url; - $query = array($this->randomName(5) => $this->randomName(5)); - $result = url($url, array('query' => $query)); - $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, t('External URL can be extended with a query string in $options.')); - - // Verify query string can be extended in an external URL. - $url = $test_url . '?drupal=awesome'; - $query = array($this->randomName(5) => $this->randomName(5)); - $result = url($url, array('query' => $query)); - $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, t('External URL query string can be extended with a custom query string in $options.')); - } -} - -/** - * Tests for the check_plain() and filter_xss() functions. - */ -class CommonXssUnitTest extends DrupalUnitTestCase { - - public static function getInfo() { - return array( - 'name' => 'String filtering tests', - 'description' => 'Confirm that check_plain(), filter_xss(), and check_url() work correctly, including invalid multi-byte sequences.', - 'group' => 'System', - ); - } - - /** - * Check that invalid multi-byte sequences are rejected. - */ - function testInvalidMultiByte() { - // Ignore PHP 5.3+ invalid multibyte sequence warning. - $text = @check_plain("Foo\xC0barbaz"); - $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"'); - $text = check_plain("Fooÿñ"); - $this->assertEqual($text, "Fooÿñ", 'check_plain() accepts valid sequence "Fooÿñ"'); - $text = filter_xss("Foo\xC0barbaz"); - $this->assertEqual($text, '', 'filter_xss() rejects invalid sequence "Foo\xC0barbaz"'); - $text = filter_xss("Fooÿñ"); - $this->assertEqual($text, "Fooÿñ", 'filter_xss() accepts valid sequence Fooÿñ'); - } - - /** - * Check that special characters are escaped. - */ - function testEscaping() { - $text = check_plain(""; + $link = l($text, $path); + $sanitized_path = check_url(url($path)); + $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path))); + } + + /* + * Tests for active class in l() function. + */ + function testLActiveClass() { + $link = l($this->randomName(), $_GET['q']); + $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); + } + + /** + * Tests for custom class in l() function. + */ + function testLCustomClass() { + $class = $this->randomName(); + $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class)))); + $this->assertTrue($this->hasClass($link, $class), t('Custom class @class is present on link when requested', array('@class' => $class))); + $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active'))); + } + + private function hasClass($link, $class) { + return preg_match('|class="([^\"\s]+\s+)*' . $class . '|', $link); + } + + /** + * Test drupal_get_query_parameters(). + */ + function testDrupalGetQueryParameters() { + $original = array( + 'a' => 1, + 'b' => array( + 'd' => 4, + 'e' => array( + 'f' => 5, + ), + ), + 'c' => 3, + 'q' => 'foo/bar', + ); + + // Default arguments. + $result = $_GET; + unset($result['q']); + $this->assertEqual(drupal_get_query_parameters(), $result, t("\$_GET['q'] was removed.")); + + // Default exclusion. + $result = $original; + unset($result['q']); + $this->assertEqual(drupal_get_query_parameters($original), $result, t("'q' was removed.")); + + // First-level exclusion. + $result = $original; + unset($result['b']); + $this->assertEqual(drupal_get_query_parameters($original, array('b')), $result, t("'b' was removed.")); + + // Second-level exclusion. + $result = $original; + unset($result['b']['d']); + $this->assertEqual(drupal_get_query_parameters($original, array('b[d]')), $result, t("'b[d]' was removed.")); + + // Third-level exclusion. + $result = $original; + unset($result['b']['e']['f']); + $this->assertEqual(drupal_get_query_parameters($original, array('b[e][f]')), $result, t("'b[e][f]' was removed.")); + + // Multiple exclusions. + $result = $original; + unset($result['a'], $result['b']['e'], $result['c']); + $this->assertEqual(drupal_get_query_parameters($original, array('a', 'b[e]', 'c')), $result, t("'a', 'b[e]', 'c' were removed.")); + } + + /** + * Test drupal_http_build_query(). + */ + function testDrupalHttpBuildQuery() { + $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', t('Value was properly encoded.')); + $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', t('Key was properly encoded.')); + $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', t('Multiple values were properly concatenated.')); + $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', t('Nested array was properly encoded.')); + } + + /** + * Test drupal_parse_url(). + */ + function testDrupalParseUrl() { + // Relative URL. + $url = 'foo/bar?foo=bar&bar=baz&baz#foo'; + $result = array( + 'path' => 'foo/bar', + 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), + 'fragment' => 'foo', + ); + $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); + + // Relative URL that is known to confuse parse_url(). + $url = 'foo/bar:1'; + $result = array( + 'path' => 'foo/bar:1', + 'query' => array(), + 'fragment' => '', + ); + $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.')); + + // Absolute URL. + $url = '/foo/bar?foo=bar&bar=baz&baz#foo'; + $result = array( + 'path' => '/foo/bar', + 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), + 'fragment' => 'foo', + ); + $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL parsed correctly.')); + + // External URL testing. + $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; + + // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. + $this->assertTrue(url_is_external($url), t('Correctly identified an external URL.')); + + // Test the parsing of absolute URLs. + $result = array( + 'path' => 'http://drupal.org/foo/bar', + 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), + 'fragment' => 'foo', + ); + $this->assertEqual(drupal_parse_url($url), $result, t('External URL parsed correctly.')); + + // Verify proper parsing of URLs when clean URLs are disabled. + $result = array( + 'path' => 'foo/bar', + 'query' => array('bar' => 'baz'), + 'fragment' => 'foo', + ); + // Non-clean URLs #1: Absolute URL generated by url(). + $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo'; + $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL with clean URLs disabled parsed correctly.')); + + // Non-clean URLs #2: Relative URL generated by url(). + $url = '?q=foo/bar&bar=baz#foo'; + $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL with clean URLs disabled parsed correctly.')); + + // Non-clean URLs #3: URL generated by url() on non-Apache webserver. + $url = 'index.php?q=foo/bar&bar=baz#foo'; + $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.')); + + // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. + $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); + $this->assertFalse(valid_url($parts['path'], TRUE), t('drupal_parse_url() correctly parsed a forged URL.')); + } + + /** + * Test url() with/without query, with/without fragment, absolute on/off and + * assert all that works when clean URLs are on and off. + */ + function testUrl() { + global $base_url; + + foreach (array(FALSE, TRUE) as $absolute) { + // Get the expected start of the path string. + $base = $absolute ? $base_url . '/' : base_path(); + $absolute_string = $absolute ? 'absolute' : NULL; + + // Disable Clean URLs. + $GLOBALS['conf']['clean_url'] = 0; + + $url = $base . '?q=node/123'; + $result = url('node/123', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . '?q=node/123#foo'; + $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . '?q=node/123&foo'; + $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . '?q=node/123&foo=bar&bar=baz'; + $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . '?q=node/123&foo#bar'; + $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base; + $result = url('', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + // Enable Clean URLs. + $GLOBALS['conf']['clean_url'] = 1; + + $url = $base . 'node/123'; + $result = url('node/123', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123#foo'; + $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo'; + $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo=bar&bar=baz'; + $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base . 'node/123?foo#bar'; + $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + + $url = $base; + $result = url('', array('absolute' => $absolute)); + $this->assertEqual($url, $result, "$url == $result"); + } + } + + /** + * Test external URL handling. + */ + function testExternalUrls() { + $test_url = 'http://drupal.org/'; + + // Verify external URL can contain a fragment. + $url = $test_url . '#drupal'; + $result = url($url); + $this->assertEqual($url, $result, t('External URL with fragment works without a fragment in $options.')); + + // Verify fragment can be overidden in an external URL. + $url = $test_url . '#drupal'; + $fragment = $this->randomName(10); + $result = url($url, array('fragment' => $fragment)); + $this->assertEqual($test_url . '#' . $fragment, $result, t('External URL fragment is overidden with a custom fragment in $options.')); + + // Verify external URL can contain a query string. + $url = $test_url . '?drupal=awesome'; + $result = url($url); + $this->assertEqual($url, $result, t('External URL with query string works without a query string in $options.')); + + // Verify external URL can be extended with a query string. + $url = $test_url; + $query = array($this->randomName(5) => $this->randomName(5)); + $result = url($url, array('query' => $query)); + $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, t('External URL can be extended with a query string in $options.')); + + // Verify query string can be extended in an external URL. + $url = $test_url . '?drupal=awesome'; + $query = array($this->randomName(5) => $this->randomName(5)); + $result = url($url, array('query' => $query)); + $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, t('External URL query string can be extended with a custom query string in $options.')); + } +} + +/** + * Tests for the check_plain() and filter_xss() functions. + */ +class CommonXssUnitTest extends DrupalUnitTestCase { + + public static function getInfo() { + return array( + 'name' => 'String filtering tests', + 'description' => 'Confirm that check_plain(), filter_xss(), and check_url() work correctly, including invalid multi-byte sequences.', + 'group' => 'System', + ); + } + + /** + * Check that invalid multi-byte sequences are rejected. + */ + function testInvalidMultiByte() { + // Ignore PHP 5.3+ invalid multibyte sequence warning. + $text = @check_plain("Foo\xC0barbaz"); + $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"'); + $text = check_plain("Fooÿñ"); + $this->assertEqual($text, "Fooÿñ", 'check_plain() accepts valid sequence "Fooÿñ"'); + $text = filter_xss("Foo\xC0barbaz"); + $this->assertEqual($text, '', 'filter_xss() rejects invalid sequence "Foo\xC0barbaz"'); + $text = filter_xss("Fooÿñ"); + $this->assertEqual($text, "Fooÿñ", 'filter_xss() accepts valid sequence Fooÿñ'); + } + + /** + * Check that special characters are escaped. + */ + function testEscaping() { + $text = check_plain("