ChadoFieldGetValuesListTest.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <?php
  2. namespace Tests\tripal_chado\fields;
  3. use StatonLab\TripalTestSuite\DBTransaction;
  4. use StatonLab\TripalTestSuite\TripalTestCase;
  5. use StatonLab\TripalTestSuite\Database\Factory;
  6. /**
  7. * Test ChadoField->getValueList() Method.
  8. */
  9. class ChadoFieldGetValuesListTest extends TripalTestCase {
  10. // Uncomment to auto start and rollback db transactions per test method.
  11. use DBTransaction;
  12. // Stores a list of field instances to be tested including their storage method and instance info.
  13. private $field_list = NULL;
  14. /**
  15. * Test getValueList for fields based on columns in the base table.
  16. *
  17. * @dataProvider getBaseFields
  18. *
  19. * @group fields
  20. * @group getValueList
  21. */
  22. public function testBaseTableColumns($field_name, $bundle_name, $info) {
  23. include_once(drupal_get_path('tripal_chado', 'module') . '/includes/TripalFields/ChadoField.inc');
  24. $faker = \Faker\Factory::create();
  25. // Construct the Field instance we want the values for.
  26. // Specifying "ChadoField" here ensures we are only testing our
  27. // implementation of getValueList() and not the custom version for any
  28. // given field.
  29. // YOU SHOULD TEST CUSTOM FIELD IMPLEMENTATIONS SEPARATELY.
  30. $instance = new \ChadoField($info['field_info'], $info['instance_info']);
  31. // Retrieve the values.
  32. // $values will be an array containing the distinct set of values for this field instance.
  33. $values = $instance->getValueList(array('limit' => 5));
  34. // Ensure we have values returned!
  35. $this->assertTrue(
  36. is_array($values),
  37. t(
  38. 'No values returned for @field_name (bundle: @bundle_name, bundle base table: @bundle_base_table, chado table: @chado_table, chado column: @chado_column).',
  39. array(
  40. '@field_name' => $field_name,
  41. '@bundle_name' => $bundle_name,
  42. '@bundle_base_table' => $info['bundle_base_table'],
  43. '@chado_table' => $info['instance_info']['settings']['chado_table'],
  44. '@chado_column' => $info['instance_info']['settings']['chado_column'],
  45. )
  46. )
  47. );
  48. // Ensure there are no more then 5 as specified in the limit above.
  49. $this->assertLessThanOrEqual(5, sizeof($values),
  50. t('Returned too many results for @field_name.', array('@field_name' => $field_name)));
  51. // Ensure a known value is in the list.
  52. // Note: The following generates fake data with a fixed value for the column this
  53. // field is based on. This allows us to check that fixed value is one of those
  54. // returned by ChadoField->getValueList().
  55. $fake_value = $this->generateFakeData($info['bundle_base_table'], $info['base_schema'], $info['instance_info']['settings']['chado_column']);
  56. if ($fake_value !== FALSE) {
  57. // Re-generate the values...
  58. $values = $instance->getValueList(array('limit' => 200));
  59. // And ensure our fake value is in the returned list.
  60. // We can only check this if all the results are returned.
  61. // As such, we set the limit quite high above and if we have
  62. // less then the limit, we will go ahead with the test.
  63. // @note: this tests all fields on TravisCI since there is no pre-existing data.
  64. if (sizeof($values) < 200) {
  65. $this->assertContains($fake_value, $values, "\nThe following array should but does not contain our fake value ('$fake_value'): '" . implode("', '", $values) . '.');
  66. }
  67. }
  68. }
  69. /**
  70. * DataProvider: a list of fields who store their data in the base table of a bundle.
  71. *
  72. * Each element describes a field instance and consists of:
  73. * - the machine name of the field (e.g. obi__organism).
  74. * - the machine name of the bundle (e.g. bio_data_17).
  75. * - an array of additional information including:
  76. * - instance_info: information about the field instance.
  77. * - field_info: information about the field.
  78. * - bundle: the TripalBundle object.
  79. * - bundle_base_table: if applicable, the chado base table the bundle stores it's data in.
  80. * - base_schema: the Tripal Schema array for the bundle_base_table.
  81. */
  82. public function getBaseFields() {
  83. // Retrieve a list of fields to test.
  84. // Note: this list is cached to improve performance.
  85. $fields = $this->retrieveFieldList();
  86. return $fields['field_chado_storage']['base'];
  87. }
  88. /**
  89. * Test for fields based on columns in the base table that are also foreign keys.
  90. *
  91. * @dataProvider getBaseFkFields
  92. * @group current
  93. * @group fields
  94. * @group getValueList
  95. */
  96. public function testBaseTableForeignKey($field_name, $bundle_name, $info) {
  97. include_once(drupal_get_path('tripal_chado', 'module') . '/includes/TripalFields/ChadoField.inc');
  98. // Construct the Field instance we want the values for.
  99. // Specifying "ChadoField" here ensures we are only testing our
  100. // implementation of getValueList() and not the custom version for any
  101. // given field.
  102. // YOU SHOULD TEST CUSTOM FIELD IMPLEMENTATIONS SEPARATELY.
  103. $instance = new \ChadoField($info['field_info'], $info['instance_info']);
  104. // Retrieve the values using defaults.
  105. // $values will be an array containing the distinct set of values for this field instance.
  106. $values = $instance->getValueList(array('limit' => 5));
  107. // Ensure we have values returned!
  108. $this->assertTrue(
  109. is_array($values),
  110. t(
  111. 'No values returned for @field_name with no label string set (bundle: @bundle_name, bundle base table: @bundle_base_table, chado table: @chado_table, chado column: @chado_column).',
  112. array(
  113. '@field_name' => $field_name,
  114. '@bundle_name' => $bundle_name,
  115. '@bundle_base_table' => $info['bundle_base_table'],
  116. '@chado_table' => $info['instance_info']['settings']['chado_table'],
  117. '@chado_column' => $info['instance_info']['settings']['chado_column'],
  118. )
  119. )
  120. );
  121. // Ensure there are no more then 5 as specified in the limit above.
  122. $this->assertLessThanOrEqual(5, sizeof($values),
  123. t('Returned too many results for @field_name.', array('@field_name' => $field_name)));
  124. // Ensure it works with a label string set.
  125. // Ensure a known value is in the list.
  126. // Note: The following generates fake data with a fixed value for the column this
  127. // field is based on. This allows us to check that fixed value is one of those
  128. // returned by ChadoField->getValueList().
  129. $fake_fk_record = $this->generateFakeData($info['bundle_base_table'], $info['base_schema'], $info['instance_info']['settings']['chado_column'], $info['fk_table']);
  130. if ($fake_fk_record !== FALSE) {
  131. // We also want to test the label string functionality.
  132. // Grab two columns at random from the related table...
  133. $schema = chado_get_schema($info['fk_table']);
  134. $fk_table_fields = array_keys($schema['fields']);
  135. $use_in_label = array_rand($fk_table_fields, 2);
  136. $column1 = $fk_table_fields[$use_in_label[0]];
  137. $column2 = $fk_table_fields[$use_in_label[1]];
  138. // The label string consists of tokens of the form [column_name].
  139. $label_string = '['.$column2.'] (['.$column1.'])';
  140. // Re-generate the values...
  141. $values = $instance->getValueList(array('limit' => 200, 'label_string' => $label_string));
  142. // And ensure our fake value is in the returned list.
  143. // We can only check this if all the results are returned.
  144. // As such, we set the limit quite high above and if we have
  145. // less then the limit, we will go ahead with the test.
  146. // @note: this tests all fields on TravisCI since there is no pre-existing data.
  147. if (sizeof($values) < 200) {
  148. $fixed_key = $fake_fk_record->{$info['fk_table'].'_id'};
  149. $this->assertArrayHasKey($fixed_key, $values, "\nThe following array should but does not contain our fake record: " . print_r($fake_fk_record, TRUE));
  150. // Now test the label of the fake record option is what we expect
  151. // based on the label string we set above.
  152. $expected_label = $fake_fk_record->{$column2} . ' (' . $fake_fk_record->{$column1} . ')';
  153. $this->assertEquals($expected_label, $values[$fixed_key], "\nThe label should have been '$label_string' with the column values filled in.");
  154. }
  155. }
  156. }
  157. /**
  158. * DataProvider: a list of fields who store their data in the base table of a bundle.
  159. *
  160. * Each element describes a field instance and consists of:
  161. * - the machine name of the field (e.g. obi__organism).
  162. * - the machine name of the bundle (e.g. bio_data_17).
  163. * - an array of additional information including:
  164. * - instance_info: information about the field instance.
  165. * - field_info: information about the field.
  166. * - bundle: the TripalBundle object.
  167. * - bundle_base_table: if applicable, the chado base table the bundle stores it's data in.
  168. * - base_schema: the Tripal Schema array for the bundle_base_table.
  169. */
  170. public function getBaseFkFields() {
  171. // Retrieve a list of fields to test.
  172. // Note: this list is cached to improve performance.
  173. $fields = $this->retrieveFieldList();
  174. return $fields['field_chado_storage']['foreign key'];
  175. }
  176. /**
  177. * Test for fields based on tables besides the base one for the bundle.
  178. * CURRENTLY RETRIEVING VALUES FOR THESE TABLES IS NOT SUPPORTED.
  179. *
  180. * @dataProvider getNonBaseFields
  181. *
  182. * @group fields
  183. * @group getValueList
  184. */
  185. public function testNonBaseTable($field_name, $bundle_name, $info) {
  186. include_once(drupal_get_path('tripal_chado', 'module') . '/includes/TripalFields/ChadoField.inc');
  187. // Construct the Field instance we want the values for.
  188. // Specifying "ChadoField" here ensures we are only testing our
  189. // implementation of getValueList() and not the custom version for any
  190. // given field.
  191. // YOU SHOULD TEST CUSTOM FIELD IMPLEMENTATIONS SEPARATELY.
  192. $instance = new \ChadoField($info['field_info'], $info['instance_info']);
  193. // Supress tripal errors
  194. putenv("TRIPAL_SUPPRESS_ERRORS=TRUE");
  195. ob_start();
  196. try {
  197. // Retrieve the values.
  198. // $values will be an array containing the distinct set of values for this field instance.
  199. $values = $instance->getValueList(array('limit' => 5));
  200. // @todo Check that we got the correct warning message.
  201. // Currently we can't check this because we need to supress the error in order to keep it from printing
  202. // but once we do, we can't access it ;-P
  203. } catch (Exception $e) {
  204. $this->fail("Although we don't support values lists for $field_name, it still shouldn't produce an exception!");
  205. }
  206. // Clean the buffer and unset tripal errors suppression
  207. ob_end_clean();
  208. putenv("TRIPAL_SUPPRESS_ERRORS");
  209. $this->assertFalse($values, "We don't support retrieving values for $field_name since it doesn't store data in the base table.");
  210. }
  211. /**
  212. * DataProvider: a list of fields who store their data in the base table of a bundle.
  213. *
  214. * Each element describes a field instance and consists of:
  215. * - the machine name of the field (e.g. obi__organism).
  216. * - the machine name of the bundle (e.g. bio_data_17).
  217. * - an array of additional information including:
  218. * - instance_info: information about the field instance.
  219. * - field_info: information about the field.
  220. * - bundle: the TripalBundle object.
  221. * - bundle_base_table: if applicable, the chado base table the bundle stores it's data in.
  222. * - base_schema: the Tripal Schema array for the bundle_base_table.
  223. */
  224. public function getNonBaseFields() {
  225. // Retrieve a list of fields to test.
  226. // Note: this list is cached to improve performance.
  227. $fields = $this->retrieveFieldList();
  228. return $fields['field_chado_storage']['referring'];
  229. }
  230. /**
  231. * Returns a list of Fields sorted by their backend, etc. for use in tests.
  232. */
  233. private function retrieveFieldList() {
  234. if ($this->field_list === NULL) {
  235. $this->field_list = array();
  236. // field_info_instances() retrieves a list of all the field instances in the current site,
  237. // indexed by the bundle it is attached to.
  238. // @todo use fake bundles here to make these tests less dependant upon the current site.
  239. $bundles = field_info_instances('TripalEntity');
  240. foreach($bundles as $bundle_name => $fields) {
  241. // Load the bundle object to later determine the chado table.
  242. $bundle = tripal_load_bundle_entity(array('name'=> $bundle_name));
  243. // For each field instance...
  244. foreach ($fields as $field_name => $instance_info) {
  245. $bundle_base_table = $base_schema = NULL;
  246. // Load the field info.
  247. $field_info = field_info_field($field_name);
  248. // Determine the storage backend.
  249. $storage = $field_info['storage']['type'];
  250. // If this field stores it's data in chado...
  251. // Determine the relationship between this field and the bundle base table.
  252. $rel = NULL;
  253. if ($storage == 'field_chado_storage') {
  254. // We need to know the table this field stores it's data in.
  255. $bundle_base_table = $bundle->data_table;
  256. // and the schema for that table.
  257. $base_schema = chado_get_schema($bundle_base_table);
  258. // and the table this field stores it's data in.
  259. $field_table = $instance_info['settings']['chado_table'];
  260. $field_column = $instance_info['settings']['chado_column'];
  261. // By default we simply assume there is some relationship.
  262. $rel = 'referring';
  263. $rel_table = NULL;
  264. // If the field and bundle store their data in the same table
  265. // then it's either a "base" or "foreign key" relationship
  266. // based on the schema.
  267. if ($bundle_base_table == $field_table) {
  268. // We assume it's not a foreign key...
  269. $rel = 'base';
  270. // and then check the schema to see if we're wrong :-)
  271. foreach ($base_schema['foreign keys'] as $schema_info) {
  272. if (isset($schema_info['columns'][ $field_column ])) {
  273. $rel = 'foreign key';
  274. $rel_table = $schema_info['table'];
  275. }
  276. }
  277. }
  278. }
  279. // Store all the info about bundle, field, instance, schema for use in the test.
  280. $info = array(
  281. 'field_name' => $field_name,
  282. 'bundle_name' => $bundle_name,
  283. 'bundle' => $bundle,
  284. 'bundle_base_table' => $bundle_base_table,
  285. 'base_schema' => $base_schema,
  286. 'field_info' => $field_info,
  287. 'instance_info' => $instance_info,
  288. 'fk_table' => $rel_table,
  289. );
  290. // Create a unique key.
  291. $key = $bundle_name . '--' . $field_name;
  292. // If this bundle uses chado and we know the fields relationship to the base
  293. // chado table, then we want to index the field list by that relationship.
  294. if ($rel) {
  295. $this->field_list[$storage][$rel][$key] = array(
  296. $field_name,
  297. $bundle_name,
  298. $info
  299. );
  300. }
  301. else {
  302. $this->field_list[$storage][$key] = array(
  303. $field_name,
  304. $bundle_name,
  305. $info
  306. );
  307. }
  308. }
  309. }
  310. }
  311. return $this->field_list;
  312. }
  313. /**
  314. * Generate fake data for a given bundle.
  315. *
  316. * If only the first parameter is provided this function adds fake data to the indicated
  317. * chado table. If the third parameter is provided the generated fake data will
  318. * have a fixed value for the indicated column.
  319. *
  320. * @return
  321. * Returns FALSE if it was unable to create fake data.
  322. */
  323. function generateFakeData($chado_table, $schema, $fixed_column = FALSE, $fk_table = FALSE) {
  324. // First, do we have a factory? We can't generate data without one...
  325. if (!Factory::exists('chado.'.$chado_table)) {
  326. return FALSE;
  327. }
  328. // Create fake data -TripalTestSuite will use faker for all values.
  329. if ($fixed_column === FALSE) {
  330. factory('chado.'.$chado_table, 50)->create();
  331. return TRUE;
  332. }
  333. // Attempt to create a fixed fake value.
  334. // This needs to match the column type in the chado table and if the column is a
  335. // foreign key, this value should match a fake record in the related table.
  336. $fake_value = NULL;
  337. // If we weren't told the related table then we assume this is a simple column (not a foreign key).
  338. if ($fk_table === FALSE) {
  339. $column_type = $schema[$fixed_column]['type'];
  340. if (($column_type == 'int')) {
  341. $fake_value = $faker->randomNumber();
  342. }
  343. elseif (($column_type == 'varchar') OR ($column_type == 'text')) {
  344. $fake_value = $faker->words(2,TRUE);
  345. }
  346. if ($fake_value !== NULL) {
  347. factory('chado.'.$chado_table)->create(array(
  348. $fixed_column => $fake_value,
  349. ));
  350. return fake_value;
  351. }
  352. }
  353. // Otherwise, we need to create a fixed fake record in the related table and then
  354. // use it in our fake data for the chado table.
  355. else {
  356. // Create our fixed fake record in the related table.
  357. $fake_table_record = factory('chado.'.$fk_table)->create();
  358. // Again, if we don't have a factory :-( there's nothing we can do.
  359. if (!Factory::exists('chado.'.$fk_table)) {
  360. return FALSE;
  361. }
  362. // Now create our fake records.
  363. if (isset($fake_table_record->{$fk_table.'_id'})) {
  364. factory('chado.'.$chado_table)->create(array(
  365. $fixed_column => $fake_table_record->{$fk_table.'_id'},
  366. ));
  367. return $fake_table_record;
  368. }
  369. }
  370. return FALSE;
  371. }
  372. }