ChadoFieldGetValuesListTest.php 17 KB

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