1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Unit tests for all Privacy Providers. 19 * 20 * @package core_privacy 21 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 namespace core_privacy\privacy; 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 use core_privacy\manager; 29 use core_privacy\local\metadata\collection; 30 use core_privacy\local\metadata\types\type; 31 use core_privacy\local\metadata\types\database_table; 32 use core_privacy\local\metadata\types\external_location; 33 use core_privacy\local\metadata\types\plugin_type_link; 34 use core_privacy\local\metadata\types\subsystem_link; 35 use core_privacy\local\metadata\types\user_preference; 36 37 /** 38 * Unit tests for all Privacy Providers. 39 * 40 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class provider_test extends \advanced_testcase { 44 /** 45 * Returns a list of frankenstyle names of core components (plugins and subsystems). 46 * 47 * @return array the array of frankenstyle component names with the relevant class name. 48 */ 49 public function get_component_list() { 50 $components = ['core' => [ 51 'component' => 'core', 52 'classname' => manager::get_provider_classname_for_component('core') 53 ]]; 54 // Get all plugins. 55 $plugintypes = \core_component::get_plugin_types(); 56 foreach ($plugintypes as $plugintype => $typedir) { 57 $plugins = \core_component::get_plugin_list($plugintype); 58 foreach ($plugins as $pluginname => $plugindir) { 59 $frankenstyle = $plugintype . '_' . $pluginname; 60 $components[$frankenstyle] = [ 61 'component' => $frankenstyle, 62 'classname' => manager::get_provider_classname_for_component($frankenstyle), 63 ]; 64 65 } 66 } 67 // Get all subsystems. 68 foreach (\core_component::get_core_subsystems() as $name => $path) { 69 if (isset($path)) { 70 $frankenstyle = 'core_' . $name; 71 $components[$frankenstyle] = [ 72 'component' => $frankenstyle, 73 'classname' => manager::get_provider_classname_for_component($frankenstyle), 74 ]; 75 } 76 } 77 return $components; 78 } 79 80 /** 81 * Test that the specified null_provider works as expected. 82 * 83 * @dataProvider null_provider_provider 84 * @param string $component The name of the component. 85 * @param string $classname The name of the class for privacy 86 */ 87 public function test_null_provider($component, $classname) { 88 $reason = $classname::get_reason(); 89 $this->assertIsString($reason); 90 91 $this->assertIsString(get_string($reason, $component)); 92 $this->assertDebuggingNotCalled(); 93 } 94 95 /** 96 * Data provider for the null_provider tests. 97 * 98 * @return array 99 */ 100 public function null_provider_provider() { 101 return array_filter($this->get_component_list(), function($component) { 102 return static::component_implements( 103 $component['classname'], 104 \core_privacy\local\metadata\null_provider::class 105 ); 106 }); 107 } 108 109 /** 110 * Test that the specified metadata_provider works as expected. 111 * 112 * @dataProvider metadata_provider_provider 113 * @param string $component The name of the component. 114 * @param string $classname The name of the class for privacy 115 */ 116 public function test_metadata_provider($component, $classname) { 117 global $DB; 118 119 $collection = new collection($component); 120 $metadata = $classname::get_metadata($collection); 121 $this->assertInstanceOf(collection::class, $metadata); 122 $this->assertSame($collection, $metadata); 123 $this->assertContainsOnlyInstancesOf(type::class, $metadata->get_collection()); 124 125 foreach ($metadata->get_collection() as $item) { 126 // All items must have a valid string name. 127 // Note: This is not a string identifier. 128 $this->assertIsString($item->get_name()); 129 130 if ($item instanceof database_table) { 131 // Check that the table is valid. 132 $this->assertTrue($DB->get_manager()->table_exists($item->get_name())); 133 } 134 135 if ($item instanceof \core_privacy\local\metadata\types\plugintype_link) { 136 // Check that plugin type is valid. 137 $this->assertTrue(array_key_exists($item->get_name(), \core_component::get_plugin_types())); 138 } 139 140 if ($item instanceof subsystem_link) { 141 // Check that core subsystem exists. 142 list($plugintype, $pluginname) = \core_component::normalize_component($item->get_name()); 143 $this->assertEquals('core', $plugintype); 144 $this->assertTrue(\core_component::is_core_subsystem($pluginname)); 145 } 146 147 if ($summary = $item->get_summary()) { 148 // Summary is optional, but when provided must be a valid string identifier. 149 $this->assertIsString($summary); 150 151 // Check that the string is also correctly defined. 152 $this->assertIsString(get_string($summary, $component)); 153 $this->assertDebuggingNotCalled(); 154 } 155 156 if ($fields = $item->get_privacy_fields()) { 157 // Privacy fields are optional, but when provided must be a valid string identifier. 158 foreach ($fields as $field => $identifier) { 159 $this->assertIsString($field); 160 $this->assertIsString($identifier); 161 162 // Check that the string is also correctly defined. 163 $this->assertIsString(get_string($identifier, $component)); 164 $this->assertDebuggingNotCalled(); 165 } 166 } 167 } 168 } 169 170 /** 171 * Test that all providers implement some form of compliant provider. 172 * 173 * @dataProvider get_component_list 174 * @param string $component frankenstyle component name, e.g. 'mod_assign' 175 * @param string $classname the fully qualified provider classname 176 */ 177 public function test_all_providers_compliant($component, $classname) { 178 $manager = new manager(); 179 $this->assertTrue($manager->component_is_compliant($component)); 180 } 181 182 /** 183 * Ensure that providers do not throw an error when processing a deleted user. 184 * 185 * @dataProvider is_user_data_provider 186 * @param string $component 187 */ 188 public function test_component_understands_deleted_users($component) { 189 $this->resetAfterTest(); 190 191 // Create a user. 192 $user = $this->getDataGenerator()->create_user(); 193 194 // Delete the user and their context. 195 delete_user($user); 196 $usercontext = \context_user::instance($user->id); 197 $usercontext->delete(); 198 199 $contextlist = manager::component_class_callback($component, \core_privacy\local\request\core_user_data_provider::class, 200 'get_contexts_for_userid', [$user->id]); 201 202 $this->assertInstanceOf(\core_privacy\local\request\contextlist::class, $contextlist); 203 } 204 205 /** 206 * Ensure that providers do not throw an error when processing a deleted user. 207 * 208 * @dataProvider is_user_data_provider 209 * @param string $component 210 */ 211 public function test_userdata_provider_implements_userlist($component) { 212 $classname = manager::get_provider_classname_for_component($component); 213 $this->assertTrue(is_subclass_of($classname, \core_privacy\local\request\core_userlist_provider::class)); 214 } 215 216 /** 217 * Data provider for the metadata\provider tests. 218 * 219 * @return array 220 */ 221 public function metadata_provider_provider() { 222 return array_filter($this->get_component_list(), function($component) { 223 return static::component_implements( 224 $component['classname'], 225 \core_privacy\local\metadata\provider::class 226 ); 227 }); 228 } 229 230 /** 231 * List of providers which implement the core_user_data_provider. 232 * 233 * @return array 234 */ 235 public function is_user_data_provider() { 236 return array_filter($this->get_component_list(), function($component) { 237 return static::component_implements( 238 $component['classname'], 239 \core_privacy\local\request\core_user_data_provider::class 240 ); 241 }); 242 } 243 244 /** 245 * Checks whether the component's provider class implements the specified interface, either directly or as a grandchild. 246 * 247 * @param string $providerclass The name of the class to test. 248 * @param string $interface the name of the interface we want to check. 249 * @return bool Whether the class implements the interface. 250 */ 251 protected static function component_implements($providerclass, $interface) { 252 if (class_exists($providerclass) && interface_exists($interface)) { 253 return is_subclass_of($providerclass, $interface); 254 } 255 256 return false; 257 } 258 259 /** 260 * Finds user fields in a table 261 * 262 * Returns fields that have foreign key to user table and fields that are named 'userid'. 263 * 264 * @param \xmldb_table $table 265 * @return array 266 */ 267 protected function get_userid_fields(\xmldb_table $table) { 268 $userfields = []; 269 270 // Find all fields that have a foreign key to 'id' field in 'user' table. 271 $keys = $table->getKeys(); 272 foreach ($keys as $key) { 273 $reffields = $key->getRefFields(); 274 $fields = $key->getFields(); 275 if ($key->getRefTable() === 'user' && count($reffields) == 1 && $reffields[0] == 'id' && count($fields) == 1) { 276 $userfields[$fields[0]] = $fields[0]; 277 } 278 } 279 // Find fields with the name 'userid' even if they don't have a foreign key. 280 $fields = $table->getFields(); 281 foreach ($fields as $field) { 282 if ($field->getName() == 'userid') { 283 $userfields['userid'] = 'userid'; 284 } 285 } 286 287 return $userfields; 288 } 289 290 /** 291 * Test that all tables with user fields are covered by metadata providers 292 */ 293 public function test_table_coverage() { 294 global $DB; 295 $dbman = $DB->get_manager(); 296 $tables = []; 297 298 foreach ($dbman->get_install_xml_files() as $filename) { 299 $xmldbfile = new \xmldb_file($filename); 300 if (!$xmldbfile->loadXMLStructure()) { 301 continue; 302 } 303 $structure = $xmldbfile->getStructure(); 304 $tablelist = $structure->getTables(); 305 306 foreach ($tablelist as $table) { 307 if ($fields = $this->get_userid_fields($table)) { 308 $tables[$table->getName()] = ' - ' . $table->getName() . ' (' . join(', ', $fields) . ')'; 309 } 310 } 311 } 312 313 $componentlist = $this->metadata_provider_provider(); 314 foreach ($componentlist as $componentarray) { 315 $component = $componentarray['component']; 316 $classname = $componentarray['classname']; 317 $collection = new collection($component); 318 $metadata = $classname::get_metadata($collection); 319 foreach ($metadata->get_collection() as $item) { 320 if ($item instanceof database_table) { 321 unset($tables[$item->get_name()]); 322 } 323 } 324 } 325 326 if ($tables) { 327 $this->fail("The following tables with user fields must be covered with metadata providers: \n". 328 join("\n", $tables)); 329 } 330 331 } 332 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body