Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}