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