Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
use core_privacy\manager;
27
use core_privacy\local\metadata\collection;
28
use core_privacy\local\metadata\types\type;
29
use core_privacy\local\metadata\types\database_table;
30
use core_privacy\local\metadata\types\subsystem_link;
31
 
1441 ariadna 32
// phpcs:disable moodle.PHPUnit.TestCaseProvider.dataProviderSyntaxMethodNotFound
33
 
1 efrain 34
/**
35
 * Unit tests for all Privacy Providers.
36
 *
37
 * @copyright   2018 Andrew Nicols <andrew@nicols.co.uk>
38
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
1441 ariadna 40
final class provider_test extends \core\tests\plugin_checks_testcase {
1 efrain 41
    /**
42
     * Returns a list of frankenstyle names of core components (plugins and subsystems).
43
     *
44
     * @return array the array of frankenstyle component names with the relevant class name.
45
     */
1441 ariadna 46
    public static function get_component_list(): array {
1 efrain 47
        $components = ['core' => [
48
            'component' => 'core',
1441 ariadna 49
            'classname' => manager::get_provider_classname_for_component('core'),
1 efrain 50
        ]];
51
        // Get all plugins.
52
        $plugintypes = \core_component::get_plugin_types();
53
        foreach ($plugintypes as $plugintype => $typedir) {
54
            $plugins = \core_component::get_plugin_list($plugintype);
55
            foreach ($plugins as $pluginname => $plugindir) {
56
                $frankenstyle = $plugintype . '_' . $pluginname;
57
                $components[$frankenstyle] = [
58
                    'component' => $frankenstyle,
59
                    'classname' => manager::get_provider_classname_for_component($frankenstyle),
60
                ];
61
 
62
            }
63
        }
64
        // Get all subsystems.
65
        foreach (\core_component::get_core_subsystems() as $name => $path) {
66
            if (isset($path)) {
67
                $frankenstyle = 'core_' . $name;
68
                $components[$frankenstyle] = [
69
                    'component' => $frankenstyle,
70
                    'classname' => manager::get_provider_classname_for_component($frankenstyle),
71
                ];
72
            }
73
        }
74
        return $components;
75
    }
76
 
77
    /**
78
     * Test that the specified null_provider works as expected.
79
     *
1441 ariadna 80
     * @group        plugin_checks
1 efrain 81
     * @dataProvider null_provider_provider
82
     * @param   string  $component The name of the component.
83
     * @param   string  $classname The name of the class for privacy
84
     */
11 efrain 85
    public function test_null_provider($component, $classname): void {
1 efrain 86
        $reason = $classname::get_reason();
87
        $this->assertIsString($reason);
88
 
89
        $this->assertIsString(get_string($reason, $component));
90
    }
91
 
92
    /**
93
     * Data provider for the null_provider tests.
94
     *
95
     * @return array
96
     */
1441 ariadna 97
    public static function null_provider_provider(): array {
98
        return array_filter(self::get_component_list(), function($component): bool {
99
            return static::component_implements(
100
                $component['classname'],
101
                \core_privacy\local\metadata\null_provider::class
102
            );
1 efrain 103
        });
104
    }
105
 
106
    /**
107
     * Test that the specified metadata_provider works as expected.
108
     *
1441 ariadna 109
     * @group        plugin_checks
1 efrain 110
     * @dataProvider metadata_provider_provider
111
     * @param   string  $component The name of the component.
112
     * @param   string  $classname The name of the class for privacy
113
     */
11 efrain 114
    public function test_metadata_provider($component, $classname): void {
1 efrain 115
        global $DB;
116
 
117
        $collection = new collection($component);
118
        $metadata = $classname::get_metadata($collection);
119
        $this->assertInstanceOf(collection::class, $metadata);
120
        $this->assertSame($collection, $metadata);
121
        $this->assertContainsOnlyInstancesOf(type::class, $metadata->get_collection());
122
 
123
        foreach ($metadata->get_collection() as $item) {
124
            // All items must have a valid string name.
125
            // Note: This is not a string identifier.
126
            $this->assertIsString($item->get_name());
127
 
128
            if ($item instanceof database_table) {
129
                // Check that the table is valid.
130
                $this->assertTrue($DB->get_manager()->table_exists($item->get_name()));
131
            }
132
 
133
            if ($item instanceof \core_privacy\local\metadata\types\plugintype_link) {
134
                // Check that plugin type is valid.
135
                $this->assertTrue(array_key_exists($item->get_name(), \core_component::get_plugin_types()));
136
            }
137
 
138
            if ($item instanceof subsystem_link) {
139
                // Check that core subsystem exists.
140
                list($plugintype, $pluginname) = \core_component::normalize_component($item->get_name());
141
                $this->assertEquals('core', $plugintype);
142
                $this->assertTrue(\core_component::is_core_subsystem($pluginname));
143
            }
144
 
145
            if ($summary = $item->get_summary()) {
146
                // Summary is optional, but when provided must be a valid string identifier.
147
                $this->assertIsString($summary);
148
 
149
                // Check that the string is also correctly defined.
150
                $this->assertIsString(get_string($summary, $component));
151
            }
152
 
153
            if ($fields = $item->get_privacy_fields()) {
154
                // Privacy fields are optional, but when provided must be a valid string identifier.
155
                foreach ($fields as $field => $identifier) {
156
                    $this->assertIsString($field);
157
                    $this->assertIsString($identifier);
158
 
159
                    // Check that the string is also correctly defined.
160
                    $this->assertIsString(get_string($identifier, $component));
161
                }
162
            }
163
        }
164
    }
165
 
166
    /**
167
     * Test that all providers implement some form of compliant provider.
168
     *
1441 ariadna 169
     * @group        plugin_checks
1 efrain 170
     * @dataProvider get_component_list
171
     * @param string $component frankenstyle component name, e.g. 'mod_assign'
172
     * @param string $classname the fully qualified provider classname
173
     */
11 efrain 174
    public function test_all_providers_compliant($component, $classname): void {
1 efrain 175
        $manager = new manager();
176
        $this->assertTrue($manager->component_is_compliant($component));
177
    }
178
 
179
    /**
180
     * Ensure that providers do not throw an error when processing a deleted user.
181
     *
1441 ariadna 182
     * @group           plugin_checks
1 efrain 183
     * @dataProvider    is_user_data_provider
184
     * @param   string  $component
185
     */
11 efrain 186
    public function test_userdata_provider_implements_userlist($component): void {
1 efrain 187
        $classname = manager::get_provider_classname_for_component($component);
188
        $this->assertTrue(is_subclass_of($classname, \core_privacy\local\request\core_userlist_provider::class));
189
    }
190
 
191
    /**
192
     * Data provider for the metadata\provider tests.
193
     *
194
     * @return array
195
     */
1441 ariadna 196
    public static function metadata_provider_provider(): array {
197
        return array_filter(self::get_component_list(), function($component): bool {
198
            return static::component_implements(
199
                $component['classname'],
200
                \core_privacy\local\metadata\provider::class
201
            );
1 efrain 202
        });
203
    }
204
 
205
    /**
206
     * List of providers which implement the core_user_data_provider.
207
     *
208
     * @return array
209
     */
1441 ariadna 210
    public static function is_user_data_provider(): array {
211
        return array_map(
212
            fn ($data) => ['component' => $data['component']],
213
            array_filter(self::get_component_list(), function($component): bool {
1 efrain 214
                return static::component_implements(
215
                    $component['classname'],
216
                    \core_privacy\local\request\core_user_data_provider::class
217
                );
1441 ariadna 218
            }),
219
        );
1 efrain 220
    }
221
 
222
    /**
223
     * Checks whether the component's provider class implements the specified interface, either directly or as a grandchild.
224
     *
225
     * @param   string  $providerclass The name of the class to test.
226
     * @param   string  $interface the name of the interface we want to check.
227
     * @return  bool    Whether the class implements the interface.
228
     */
229
    protected static function component_implements($providerclass, $interface) {
230
        if (class_exists($providerclass) && interface_exists($interface)) {
231
            return is_subclass_of($providerclass, $interface);
232
        }
233
 
234
        return false;
235
    }
236
 
237
    /**
238
     * Finds user fields in a table
239
     *
240
     * Returns fields that have foreign key to user table and fields that are named 'userid'.
241
     *
242
     * @param \xmldb_table $table
243
     * @return array
244
     */
245
    protected function get_userid_fields(\xmldb_table $table) {
246
        $userfields = [];
247
 
248
        // Find all fields that have a foreign key to 'id' field in 'user' table.
249
        $keys = $table->getKeys();
250
        foreach ($keys as $key) {
251
            $reffields = $key->getRefFields();
252
            $fields = $key->getFields();
253
            if ($key->getRefTable() === 'user' && count($reffields) == 1 && $reffields[0] == 'id' && count($fields) == 1) {
254
                $userfields[$fields[0]] = $fields[0];
255
            }
256
        }
257
        // Find fields with the name 'userid' even if they don't have a foreign key.
258
        $fields = $table->getFields();
259
        foreach ($fields as $field) {
260
            if ($field->getName() == 'userid') {
261
                $userfields['userid'] = 'userid';
262
            }
263
        }
264
 
265
        return $userfields;
266
    }
267
 
268
    /**
269
     * Test that all tables with user fields are covered by metadata providers
1441 ariadna 270
     *
271
     * @group        plugin_checks
272
     * @dataProvider get_component_list
273
     * @coversNothing
274
     * @param string $component frankenstyle component name, e.g. 'mod_assign'
275
     * @param string $classname the fully qualified provider classname
1 efrain 276
     */
1441 ariadna 277
    public function test_table_coverage(string $component, string $classname): void {
1 efrain 278
        global $DB;
1441 ariadna 279
        $dbman = $DB->get_manager(); // Load DDL classes.
1 efrain 280
        $tables = [];
281
 
1441 ariadna 282
        $filename = \core_component::get_component_directory($component) . '/db/install.xml';
283
        if (!file_exists($filename)) {
284
            $this->expectNotToPerformAssertions();
285
            return;
286
        }
287
        $xmldbfile = new \xmldb_file($filename);
288
        $this->assertTrue($xmldbfile->loadXMLStructure());;
1 efrain 289
 
1441 ariadna 290
        $structure = $xmldbfile->getStructure();
291
        $tablelist = $structure->getTables();
292
 
293
        foreach ($tablelist as $table) {
294
            if ($fields = $this->get_userid_fields($table)) {
295
                $tables[$table->getName()] = '  - ' . $table->getName() . ' (' . join(', ', $fields) . ')';
1 efrain 296
            }
297
        }
298
 
1441 ariadna 299
        $componentlist = self::metadata_provider_provider();
1 efrain 300
        foreach ($componentlist as $componentarray) {
301
            $component = $componentarray['component'];
302
            $classname = $componentarray['classname'];
303
            $collection = new collection($component);
304
            $metadata = $classname::get_metadata($collection);
305
            foreach ($metadata->get_collection() as $item) {
306
                if ($item instanceof database_table) {
307
                    unset($tables[$item->get_name()]);
308
                }
309
            }
310
        }
311
 
312
        if ($tables) {
313
            $this->fail("The following tables with user fields must be covered with metadata providers: \n".
314
                join("\n", $tables));
315
        }
316
 
317
    }
318
}