diff --git a/src/Drush/Generators/Tests/ChadoFieldTypeGenerator.php b/src/Drush/Generators/Tests/ChadoFieldTypeGenerator.php new file mode 100644 index 0000000..979452b --- /dev/null +++ b/src/Drush/Generators/Tests/ChadoFieldTypeGenerator.php @@ -0,0 +1,302 @@ +prompt = $this->createInterviewer($vars); + + // Module Machine Name. + $vars['machine_name'] = $this->prompt->askMachineName(); + + // Chado Version. + $vars['chado_version'] = $this->prompt->ask('Chado Version', '1.3.3.013'); + + // Ask what file to save the test in. + $vars['test_class_prefix'] = $this->prompt->ask('What should be the prefix used to name the test classes generated?', 'BaseField'); + $vars['test_CRUD_class'] = $vars['test_class_prefix'] . 'CRUDTest'; + $vars['test_CRUD_yml'] = $vars['test_class_prefix'] . 'CRUD-TestInfo.yml'; + $vars['test_methods_class'] = $vars['test_class_prefix'] . 'MethodsTest'; + $vars['test_methods_yml'] = $vars['test_class_prefix'] . 'Methods-TestInfo.yml'; + $vars['test_path'] = 'tests/src/Kernel/Plugin/ChadoField/FieldType'; + + $this->generateCrudTest($vars, $assets); + $this->generateMethodsTest($vars, $assets); + } + + /** + * Generates the field test focused on testing create-update-delete (CRUD). + * + * @param array $vars + * The variables collected from the user. + * @see self::generate() + * @param \DrupalCodeGenerator\Asset\AssetCollection $assets + * The assets generated by the generator as a whole. + * + * @return void + * No need to return anything as all params are passed by reference. + */ + protected function generateCrudTest(array &$vars, Assets &$assets): void { + + print "\nThe following questions are specific to testing the create-update-delete (CRUD) of the indicated field.\n"; + + // Now start building the test information yaml file. + $test_info_yaml = [ + 'system-under-test' => [ + 'chado_version' => $vars['chado_version'], + 'bundle' => [], + 'fields' => [], + ], + 'scenarios' => [], + ]; + + // Bundle Information. + $vars['bundle_id'] = $this->prompt->ask('Existing Tripal Content Type to test fields on', 'organism'); + $this->addBundleInfo($vars['bundle_id'], $test_info_yaml); + + // For each field attached to this bundle, ask if they want to add it to + // the system under test. + $dont_prompt_fields = $this->prompt->confirm('Do you want to exclude some fields for this content type', FALSE); + foreach ($this->field_definitions as $field_name => $field_defn) { + if (get_class($field_defn) == 'Drupal\field\Entity\FieldConfig') { + if (($dont_prompt_fields === FALSE) or $this->prompt->confirm("\tAdd $field_name to the system-under-test?")) { + print "\tAdding $field_name to the system-under-test.\n"; + $this->addFieldInfo($vars['bundle_id'], $field_defn, $test_info_yaml); + } + } + } + + // Now add a scenario based on the default values. + $this->addDefaultScenario($vars, $test_info_yaml); + + // We are now done generating the yaml file so lets create that. + $yaml_file = $assets->addFile('{test_path}/{test_CRUD_yml}'); + $yaml_file->content(Yaml::dump($test_info_yaml, 8, 2, Yaml::DUMP_COMPACT_NESTED_MAPPING)); + + // Then lets create the test file. + $assets->addFile('{test_path}/{test_CRUD_class}.php', 'chado-field-type-crud-test.twig'); + } + + /** + * Generates the field test focused on testing methods directly. + * + * @param array $vars + * The variables collected from the user. + * @see self::generate() + * @param \DrupalCodeGenerator\Asset\AssetCollection $assets + * The assets generated by the generator as a whole. + * + * @return void + * No need to return anything as all params are passed by reference. + */ + protected function generateMethodsTest(array &$vars, Assets &$assets): void { + + print "\nThe following questions are specific to testing various methods of the indicated field.\n"; + + // Now start building the test information yaml file. + $test_info_yaml = [ + 'system-under-test' => [ + 'chado_version' => $vars['chado_version'], + ], + 'scenarios' => [], + ]; + + // We are now done generating the yaml file so lets create that. + $yaml_file = $assets->addFile('{test_path}/{test_methods_yml}'); + $yaml_file->content(Yaml::dump($test_info_yaml, 8, 2, Yaml::DUMP_COMPACT_NESTED_MAPPING)); + + // Then lets create the test file. + $assets->addFile('{test_path}/{test_methods_class}.php', 'chado-field-type-methods-test.twig'); + } + + /** + * Looks-up the bundle and adds information about it to the system under test. + * + * NOTE: This also populates the field_definitions and field_type_definitions + * properties to allow field information to be accessed easily from other + * methods. + * + * @param string $bundle_id + * The ID of an existing TripalEntityType (e.g. organism). + * @param array $test_info_yaml + * The current test information yaml array from the generate method. + * + * @return \Drupal\tripal\Entity\TripalEntityType + * The bundle object used to fill out the test info yaml array. + */ + protected function addBundleInfo(string $bundle_id, array &$test_info_yaml): TripalEntityType { + + // First we need to get the bundle object. + $bundle = \Drupal::entityTypeManager()->getStorage('tripal_entity_type')->load($bundle_id); + if (!is_object($bundle)) { + throw new \UnexpectedValueException('The Tripal Content Type must already exist in the current site. Make sure you have imported the type collection.'); + } + $this->bundle = $bundle; + + // Get all the fields for this bundle. + $this->field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('tripal_entity', $bundle_id); + // - We need the Field Type Plugin Manager to get the class. + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + $this->field_type_definitions = $field_type_manager->getDefinitions(); + + // Add the bundle info to the yaml array. + $test_info_yaml['system-under-test']['bundle']['label'] = $bundle->getLabel(); + $test_info_yaml['system-under-test']['bundle']['termIdSpace'] = $bundle->getTermIdSpace(); + $test_info_yaml['system-under-test']['bundle']['termAccession'] = $bundle->getTermAccession(); + $test_info_yaml['system-under-test']['bundle']['id'] = $bundle->getID(); + $test_info_yaml['system-under-test']['bundle']['settings'] = $bundle->getThirdPartySettings('tripal'); + + return $bundle; + } + + /** + * Adds field to the system under test after confirmation. + * + * @param string $bundle_id + * The ID of an existing TripalEntityType (e.g. organism). + * @param \Drupal\field\Entity\FieldConfig $field_defn + * The definition of the field you would like to add to the config. + * @param array $test_info_yaml + * The current test information yaml array from the generate method. + */ + protected function addFieldInfo(string $bundle_id, FieldConfig $field_defn, array &$test_info_yaml) { + + $field_name = $field_defn->getName(); + $field_storage_defn = $field_defn->getFieldStorageDefinition(); + $field_yaml = []; + + // Get the field type information. + $field_type = $field_defn->getType(); + $field_type_defn = $this->field_type_definitions[$field_type]; + + // Basic field type info. + $field_yaml['name'] = $field_name; + $field_yaml['type'] = $field_defn->getType(); + $field_yaml['type_class'] = $field_type_defn['class']; + $field_yaml['cardinality'] = $field_storage_defn->getCardinality(); + + // Field Widget and formatter information. + $field_yaml['widget'] = $field_type_defn['default_widget']; + $field_yaml['formatter'] = $field_type_defn['default_formatter']; + + // Field settings such as term and storage info. + $field_settings = $field_defn->getSettings(); + $field_yaml['termIdSpace'] = $field_settings['termIdSpace']; + $field_yaml['termAccession'] = $field_settings['termAccession']; + $field_yaml['settings'] = $field_settings; + $test_info_yaml['system-under-test']['fields'][] = $field_yaml; + + } + + /** + * Adds the default scenario to the yaml based on the system-under-test. + * + * @param array $vars + * The variables already set by the command. + * @param array $test_info_yaml + * The current test information yaml array from the generate method. + */ + protected function addDefaultScenario(array $vars, array &$test_info_yaml) { + + $scenario = [ + 'label' => 'Default Values Only', + 'description' => 'Creates a page using only default values for each field.', + ]; + + foreach (['create', 'edit'] as $first_lvl) { + $scenario[$first_lvl] = [ + 'user_input' => [], + 'expected' => [], + ]; + foreach ($test_info_yaml['system-under-test']['fields'] as $field_yml) { + $field_name = $field_yml['name']; + $field_class = $field_yml['type_class']; + // Get each field to generate some sample values for testing. + $sample_value = $field_class::generateSampleValue($this->field_definitions[$field_name]); + if ($sample_value === NULL) { + $sample_value = []; + } + // Now set the sample value for both our input and expected. + $scenario[$first_lvl]['user_input'][$field_name] = $sample_value; + $scenario[$first_lvl]['expected'][$field_name] = $sample_value; + // We also need to update the record_id to match expectations. + if ($first_lvl == 'create') { + $user_input_record_id = 0; + $expected_record_id = 1; + } + else { + $user_input_record_id = 1; + $expected_record_id = 1; + } + // If single value field then set record_id directly. + if (array_key_exists('record_id', $scenario[$first_lvl]['user_input'][$field_name])) { + $scenario[$first_lvl]['user_input'][$field_name]['record_id'] = $user_input_record_id; + $scenario[$first_lvl]['expected'][$field_name]['record_id'] = $expected_record_id; + } + // If multi-value field then set record_id for each item. + elseif (array_key_exists(0, $scenario[$first_lvl]['user_input'][$field_name]) and array_key_exists('record_id', $scenario[$first_lvl]['user_input'][$field_name][0])) { + foreach (array_keys($scenario[$first_lvl]['user_input'][$field_name]) as $delta) { + $scenario[$first_lvl]['user_input'][$field_name][$delta]['record_id'] = $user_input_record_id; + $scenario[$first_lvl]['expected'][$field_name][$delta]['record_id'] = $expected_record_id; + } + } + } + } + + $test_info_yaml['scenarios'][] = $scenario; + + } + +} diff --git a/templates/generator/tests/chado-field-type-crud-test.twig b/templates/generator/tests/chado-field-type-crud-test.twig new file mode 100644 index 0000000..351bf49 --- /dev/null +++ b/templates/generator/tests/chado-field-type-crud-test.twig @@ -0,0 +1,200 @@ +drupal_connection = $this->container->get('database'); + + // First retrieve info from the YAML file for this particular test. + [$this->system_under_test, $this->scenarios] = $this->getTestInfoFromYaml($this->yaml_info_file); + $this->bundle_name = $this->system_under_test['bundle']['id']; + + // Create the test Chado installation we will be using. + if (!array_key_exists('chado_version', $this->system_under_test)) { + $this->system_under_test['chado_version'] = '1.3'; + } + $this->chado_connection = $this->getTestSchema( + ChadoTestKernelBase::PREPARE_TEST_CHADO, + $this->system_under_test['chado_version'] + ); + + // Next setup the environment according to the system under test. + $this->setupChadoEntityFieldTestEnvironment($this->system_under_test); + $this->installSchema('tripal_chado', ['tripal_custom_tables', 'tripal_mviews']); + } + + /** + * Data Provider: works with the YAML to provide scenarios for testing. + * + * @return array + * List of scenarios to test where each one matches a key and label in the + * associated YAML scenarios. + */ + public static function provideScenarios() { + $scenarios = []; + + $scenarios[] = [ + 0, + "Default Values Only", + ]; + + return $scenarios; + } + + /** + * Tests the field through TripalEntity->save(). + * + * @param int $current_scenario_key + * The key of the scenario in the YAML. + * @param string $current_scenario_label + * The label of the scenario in the YAML. + * + * @dataProvider provideScenarios + */ + #[DataProvider('provideScenarios')] + public function testFieldTypeCrud(int $current_scenario_key, string $current_scenario_label) { + + // Retrieve the correct scenario. + $current_scenario = $this->scenarios[$current_scenario_key]; + $this->assertEquals($current_scenario_label, $current_scenario['label'], "We may not have retrieved the expected scenario as the labels did not match."); + + // 1. Create the entity with that value set. + $entity = TripalEntity::create([ + 'title' => $this->randomString(), + 'type' => $this->bundle_name, + ] + $current_scenario['create']['user_input']); + $this->assertInstanceOf(TripalEntity::class, $entity, "We were not able to create a piece of tripal content to test our " . $current_scenario['label'] . " scenario."); + $status = $entity->save(); + $this->assertEquals(SAVED_NEW, $status, "We expected to have saved a new entity for our " . $current_scenario['label'] . " scenario."); + + // @debug print_r($entity->toArray()); + // 2. Load the entity we just created so we can check the values. + $created_entity = TripalEntity::load($entity->id()); + $this->assertFieldValuesMatch($current_scenario['create']['expected'], $created_entity, $current_scenario['label'] . ' CREATE '); + + // 3. Make changes and then save again. + foreach ($current_scenario['edit']['user_input'] as $field_name => $new_values) { + $created_entity->set($field_name, $new_values); + } + // @debug print_r($created_entity->toArray()); + $status = $created_entity->save(); + $this->assertEquals(SAVED_UPDATED, $status, "We expected to have updated the existing entity for our " . $current_scenario['label'] . " scenario."); + + // 4. Load the entity we just updated so we can check the values. + $updated_entity = TripalEntity::load($created_entity->id()); + // @debug print_r($updated_entity->toArray()); + $this->assertFieldValuesMatch($current_scenario['edit']['expected'], $updated_entity, $current_scenario['label'] . ' EDIT '); + } + +} diff --git a/templates/generator/tests/chado-field-type-methods-test.twig b/templates/generator/tests/chado-field-type-methods-test.twig new file mode 100644 index 0000000..e72a757 --- /dev/null +++ b/templates/generator/tests/chado-field-type-methods-test.twig @@ -0,0 +1,207 @@ +drupal_connection = $this->container->get('database'); + + // First retrieve info from the YAML file for this particular test. + [$this->system_under_test, $this->scenarios] = $this->getTestInfoFromYaml($this->yaml_info_file); + $this->bundle_name = $this->system_under_test['bundle']['id']; + + // Create the test Chado installation we will be using. + if (!array_key_exists('chado_version', $this->system_under_test)) { + $this->system_under_test['chado_version'] = '1.3'; + } + $this->chado_connection = $this->getTestSchema( + ChadoTestKernelBase::PREPARE_TEST_CHADO, + $this->system_under_test['chado_version'] + ); + + // Next setup the environment according to the system under test. + $this->setupChadoEntityFieldTestEnvironment($this->system_under_test); + $this->installSchema('tripal_chado', ['tripal_custom_tables', 'tripal_mviews']); + } + + /** + * Data Provider: works with the YAML to provide scenarios for testing. + * + * @return array + * List of scenarios to test where each one matches a key and label in the + * associated YAML scenarios. + */ + public static function provideScenarios() { + $scenarios = []; + + $scenarios[] = [ + 0, + "Default Values Only", + ]; + + return $scenarios; + } + + /** + * Tests non-CRUD methods for the field types. + * + * Specifically, + * - generateSampleValue() + * - isCompatible() + * - getConstraints() + */ + public function testFieldTypeHelperMethods() { + // Retrieve the first scenario. + $current_scenario = $this->getYamlScenario(0, 'Default Values Only'); + + // Create the entity with that value set. + $entity = TripalEntity::create([ + 'title' => $this->randomString(), + 'type' => $this->bundle_name, + ] + $current_scenario['create']['user_input']); + $this->assertInstanceOf(TripalEntity::class, $entity, "We were not able to create a piece of tripal content to test our " . $current_scenario['label'] . " scenario."); + $status = $entity->save(); + $this->assertEquals(SAVED_NEW, $status, "We expected to have saved a new entity for our " . $current_scenario['label'] . " scenario."); + + // Create another content type with a chado_table not compatible with + // any fields. + $sad_bundle = $this->createTripalContentType(); + $this->chado_connection->query('CREATE TABLE IF NOT EXISTS {1:emptytable} (amount real)'); + $sad_bundle->setThirdPartySetting('tripal', 'chado_base_table', 'emptytable'); + $sad_bundle->save(); + + // For each of the fields in the system under test... + foreach ($this->system_under_test['fields'] as $field_info) { + $field_name = $field_info['name']; + $field_class = $field_info['type_class']; + $field_defn = $this->fieldConfig[$field_name]; + $field_item = $entity->get($field_name)->first(); + + // Check that we can generate a sample value. + $generated_value = $field_class::generateSampleValue($field_defn); + $this->assertIsArray($generated_value, "We expected $field_name::generateSampleValue() to generate an array."); + $this->assertArrayHasKey('record_id', $generated_value[0], "We expected the $field_name generated value to have a record_id."); + + // Check that we can confirm it is compatible with the current bundle. + $compatible = $field_item->isCompatible($entity->getBundle()); + $this->assertTrue($compatible, "We expect the $field_name field to be compatible with the current bundle (i.e. " . $this->bundle_name . ")."); + + // Check that it is not compatible with a bundle that has no compatible + // columns for any field. + $compatible = $field_item->isCompatible($sad_bundle); + $this->assertFalse($compatible, "We don't expect the $field_name field to be compatible with a bundle that has no chado table set."); + + // Check that we can retrieve constraints. + $constraints = $field_item->getConstraints(); + $this->assertIsArray($constraints, "We expected to at least be given an empty array when trying to retrieve the constraints for $field_name."); + } + } + +}