Proyectos de Subversion Moodle

Rev

Rev 1 | | 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
namespace quizaccess_seb;
18
 
19
/**
20
 * PHPUnit for property_list class.
21
 *
22
 * @package   quizaccess_seb
23
 * @author    Andrew Madden <andrewmadden@catalyst-au.net>
24
 * @copyright 2020 Catalyst IT
25
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
27
class property_list_test extends \advanced_testcase {
28
 
29
    /**
30
     * Test that an empty PList with a root dictionary is created.
31
     */
11 efrain 32
    public function test_create_empty_plist(): void {
1 efrain 33
        $emptyplist = new property_list();
34
        $xml = trim($emptyplist->to_xml());
35
        $this->assertEquals('<?xml version="1.0" encoding="UTF-8"?>
36
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
37
<plist version="1.0"><dict/></plist>', $xml);
38
    }
39
 
40
    /**
41
     * Test that a Plist is constructed from an XML string.
42
     */
11 efrain 43
    public function test_construct_plist_from_xml(): void {
1 efrain 44
        $xml = $this->get_plist_xml_header()
45
            . "<key>testKey</key>"
46
            . "<string>testValue</string>"
47
            . $this->get_plist_xml_footer();
48
        $plist = new property_list($xml);
49
        $generatedxml = trim($plist->to_xml());
50
        $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
51
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
52
<plist version=\"1.0\"><dict><key>testKey</key><string>testValue</string></dict></plist>", $generatedxml);
53
    }
54
 
55
    /**
56
     * Test that an element can be added to the root dictionary.
57
     */
11 efrain 58
    public function test_add_element_to_root(): void {
1 efrain 59
        $plist = new property_list();
60
        $newelement = new \CFPropertyList\CFString('testValue');
61
        $plist->add_element_to_root('testKey', $newelement);
62
        $generatedxml = trim($plist->to_xml());
63
        $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
64
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
65
<plist version=\"1.0\"><dict><key>testKey</key><string>testValue</string></dict></plist>", $generatedxml);
66
    }
67
 
68
    /**
69
     * Test that an element's value can be retrieved.
70
     */
11 efrain 71
    public function test_get_element_value(): void {
1 efrain 72
        $xml = $this->get_plist_xml_header()
73
                . "<key>testKey</key>"
74
                . "<string>testValue</string>"
75
                . $this->get_plist_xml_footer();
76
        $plist = new property_list($xml);
77
        $this->assertEquals('testValue', $plist->get_element_value('testKey'));
78
    }
79
 
80
    /**
81
     * Test that an element's value can be retrieved.
82
     */
11 efrain 83
    public function test_get_element_value_if_not_exists(): void {
1 efrain 84
        $plist = new property_list();
85
        $this->assertEmpty($plist->get_element_value('testKey'));
86
    }
87
 
88
    /**
89
     * Test an element's value can be retrieved if it is an array.
90
     */
11 efrain 91
    public function test_get_element_value_if_array(): void {
1 efrain 92
        $xml = $this->get_plist_xml_header()
93
            . "<key>testDict</key>"
94
            . "<dict>"
95
            . "<key>testKey</key>"
96
            . "<string>testValue</string>"
97
            . "</dict>"
98
            . $this->get_plist_xml_footer();
99
        $plist = new property_list($xml);
100
        $this->assertEquals(['testKey' => 'testValue'], $plist->get_element_value('testDict'));
101
    }
102
 
103
    /**
104
     * Test that a element's value can be updated that is not an array or dictionary.
105
     *
106
     * @param string $xml XML to create PList.
107
     * @param string $key Key of element to try and update.
108
     * @param mixed $value Value to try to update with.
109
     *
110
     * @dataProvider good_update_data_provider
111
     */
11 efrain 112
    public function test_updating_element_value($xml, $key, $value): void {
1 efrain 113
        $xml = $this->get_plist_xml_header()
114
            . $xml
115
            . $this->get_plist_xml_footer();
116
        $plist = new property_list($xml);
117
        $plist->update_element_value($key, $value);
118
        $this->assertEquals($value, $plist->get_element_value($key));
119
    }
120
 
121
    /**
122
     * Test that a element's value can be updated that is not an array or dictionary.
123
     *
124
     * @param string $xml XML to create PList.
125
     * @param string $key Key of element to try and update.
126
     * @param mixed $value Bad value to try to update with.
127
     * @param mixed $expected Expected value of element after update is called.
128
     * @param string $exceptionmessage Message of exception expected to be thrown.
129
 
130
     * @dataProvider bad_update_data_provider
131
     */
11 efrain 132
    public function test_updating_element_value_with_bad_data(string $xml, string $key, $value, $expected, $exceptionmessage): void {
1 efrain 133
        $xml = $this->get_plist_xml_header()
134
            . $xml
135
            . $this->get_plist_xml_footer();
136
        $plist = new property_list($xml);
137
 
138
        $this->expectException(\invalid_parameter_exception::class);
139
        $this->expectExceptionMessage($exceptionmessage);
140
 
141
        $plist->update_element_value($key, $value);
142
        $plistarray = json_decode($plist->to_json()); // Export elements.
143
        $this->assertEquals($expected, $plistarray->$key);
144
    }
145
 
146
    /**
147
     * Test that a dictionary can have it's value (array) updated.
148
     */
11 efrain 149
    public function test_updating_element_array_if_dictionary(): void {
1 efrain 150
        $xml = $this->get_plist_xml_header()
151
            . "<key>testDict</key>"
152
            . "<dict>"
153
            . "<key>testKey</key>"
154
            . "<string>testValue</string>"
155
            . "</dict>"
156
            . $this->get_plist_xml_footer();
157
        $plist = new property_list($xml);
158
        $plist->update_element_array('testDict', ['newKey' => new \CFPropertyList\CFString('newValue')]);
159
        $this->assertEquals(['newKey' => 'newValue'], $plist->get_element_value('testDict'));
160
    }
161
 
162
    /**
163
     * Test that a dictionary can have it's value (array) updated.
164
     */
11 efrain 165
    public function test_updating_element_array_if_dictionary_with_bad_data(): void {
1 efrain 166
        $xml = $this->get_plist_xml_header()
167
            . "<key>testDict</key>"
168
            . "<dict>"
169
            . "<key>testKey</key>"
170
            . "<string>testValue</string>"
171
            . "</dict>"
172
            . $this->get_plist_xml_footer();
173
        $plist = new property_list($xml);
174
 
175
        $this->expectException(\invalid_parameter_exception::class);
176
        $this->expectExceptionMessage('New array must only contain CFType objects.');
177
 
178
        $plist->update_element_array('testDict', [false]);
179
        $this->assertEquals(['testKey' => 'testValue'], $plist->get_element_value('testDict'));
180
        $this->assertDebuggingCalled('property_list: If updating an array in PList, it must only contain CFType objects.',
181
                DEBUG_DEVELOPER);
182
    }
183
 
184
    /**
185
     * Test that an element can be deleted.
186
     */
11 efrain 187
    public function test_delete_element(): void {
1 efrain 188
        $xml = $this->get_plist_xml_header()
189
            . "<key>testKey</key>"
190
            . "<string>testValue</string>"
191
            . $this->get_plist_xml_footer();
192
        $plist = new property_list($xml);
193
        $plist->delete_element('testKey');
194
        $generatedxml = trim($plist->to_xml());
195
        $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
196
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
197
<plist version=\"1.0\"><dict/></plist>", $generatedxml);
198
    }
199
 
200
    /**
201
     * Test that an element can be deleted.
202
     */
11 efrain 203
    public function test_delete_element_if_not_exists(): void {
1 efrain 204
        $xml = $this->get_plist_xml_header()
205
            . "<key>testKey</key>"
206
            . "<string>testValue</string>"
207
            . $this->get_plist_xml_footer();
208
        $plist = new property_list($xml);
209
        $plist->delete_element('nonExistentKey');
210
        $generatedxml = trim($plist->to_xml());
211
        // The xml should be unaltered.
212
        $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
213
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
214
<plist version=\"1.0\"><dict><key>testKey</key><string>testValue</string></dict></plist>", $generatedxml);
215
    }
216
 
217
    /**
218
     * Test that json is exported correctly according to SEB Config Key requirements.
219
     *
220
     * @param string $xml PList XML used to generate CFPropertyList.
221
     * @param string $expectedjson Expected JSON output.
222
     *
223
     * @dataProvider json_data_provider
224
     */
11 efrain 225
    public function test_export_to_json($xml, $expectedjson): void {
1 efrain 226
        $xml = $this->get_plist_xml_header()
227
            . $xml
228
            . $this->get_plist_xml_footer();
229
        $plist = new property_list($xml);
230
        $generatedjson = $plist->to_json();
231
        $this->assertEquals($expectedjson, $generatedjson);
232
    }
233
 
234
    /**
235
     * Test that the xml is exported to JSON from a real SEB config file. Expected JSON extracted from SEB logs.
236
     */
11 efrain 237
    public function test_export_to_json_full_file(): void {
1 efrain 238
        $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted_mac_001.seb');
239
        $plist = new property_list($xml);
240
        $plist->delete_element('originatorVersion'); // JSON should not contain originatorVersion key.
241
        $generatedjson = $plist->to_json();
242
        $json = trim(file_get_contents(__DIR__ . '/fixtures/JSON_unencrypted_mac_001.txt'));
243
        $this->assertEquals($json, $generatedjson);
244
    }
245
 
246
    /**
247
     * Test the set_or_update_value function.
248
     */
11 efrain 249
    public function test_set_or_update_value(): void {
1 efrain 250
        $plist = new property_list();
251
 
252
        $this->assertEmpty($plist->get_element_value('string'));
253
        $this->assertEmpty($plist->get_element_value('bool'));
254
        $this->assertEmpty($plist->get_element_value('number'));
255
 
256
        // Setting values.
257
        $plist->set_or_update_value('string', new \CFPropertyList\CFString('initial string'));
258
        $plist->set_or_update_value('bool', new \CFPropertyList\CFBoolean(true));
259
        $plist->set_or_update_value('number', new \CFPropertyList\CFNumber('10'));
260
 
261
        $this->assertEquals('initial string', $plist->get_element_value('string'));
262
        $this->assertEquals(true, $plist->get_element_value('bool'));
263
        $this->assertEquals(10, $plist->get_element_value('number'));
264
 
265
        // Updating values.
266
        $plist->set_or_update_value('string', new \CFPropertyList\CFString('new string'));
267
        $plist->set_or_update_value('bool', new \CFPropertyList\CFBoolean(false));
268
        $plist->set_or_update_value('number', new \CFPropertyList\CFNumber('42'));
269
 
270
        $this->assertEquals('new string', $plist->get_element_value('string'));
271
        $this->assertEquals(false, $plist->get_element_value('bool'));
272
        $this->assertEquals(42, $plist->get_element_value('number'));
273
 
274
        // Type exception.
275
        $this->expectException(\TypeError::class);
276
        $plist->set_or_update_value('someKey', 'We really need to pass in CFTypes here');
277
    }
278
 
279
    /**
280
     * Get a valid PList header. Must also use footer.
281
     *
282
     * @return string
283
     */
284
    private function get_plist_xml_header(): string {
285
        return "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
286
                . "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
287
                . "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
288
                . "<plist version=\"1.0\">\n"
289
                . "  <dict>";
290
    }
291
 
292
    /**
293
     * Get a valid PList footer. Must also use header.
294
     *
295
     * @return string
296
     */
297
    private function get_plist_xml_footer(): string {
298
        return "  </dict>\n"
299
                . "</plist>";
300
    }
301
 
302
    /**
303
     * Data provider for good data on update.
304
     *
305
     * @return array Array with test data.
306
     */
307
    public function good_update_data_provider(): array {
308
        return [
309
            'Update string' => ['<key>testKey</key><string>testValue</string>', 'testKey', 'newValue'],
310
            'Update bool' => ['<key>testKey</key><true/>', 'testKey', false],
311
            'Update number' => ['<key>testKey</key><real>888</real>', 'testKey', 123.4],
312
        ];
313
    }
314
 
315
    /**
316
     * Data provider for bad data on update.
317
     *
318
     * @return array Array with test data.
319
     */
320
    public function bad_update_data_provider(): array {
321
 
322
        return [
323
            'Update string with bool' => ['<key>testKey</key><string>testValue</string>', 'testKey', true, 'testValue',
324
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
325
                    . 'or value type does not match element type: CFPropertyList\CFString'],
326
            'Update string with number' => ['<key>testKey</key><string>testValue</string>', 'testKey', 999, 'testValue',
327
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
328
                    . 'or value type does not match element type: CFPropertyList\CFString'],
329
            'Update string with null' => ['<key>testKey</key><string>testValue</string>', 'testKey', null, 'testValue',
330
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
331
                    . 'or value type does not match element type: CFPropertyList\CFString'],
332
            'Update string with array' => ['<key>testKey</key><string>testValue</string>', 'testKey', ['arrayValue'], 'testValue',
333
                    'Use update_element_array to update a collection.'],
334
            'Update bool with string' => ['<key>testKey</key><true/>', 'testKey', 'testValue', true,
335
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
336
                    . 'or value type does not match element type: CFPropertyList\CFBool'],
337
            'Update bool with number' => ['<key>testKey</key><true/>', 'testKey', 999, true,
338
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
339
                    . 'or value type does not match element type: CFPropertyList\CFBool'],
340
            'Update bool with null' => ['<key>testKey</key><true/>', 'testKey', null, true,
341
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
342
                    . 'or value type does not match element type: CFPropertyList\CFBool'],
343
            'Update bool with array' => ['<key>testKey</key><true/>', 'testKey', ['testValue'], true,
344
                    'Use update_element_array to update a collection.'],
345
            'Update number with string' => ['<key>testKey</key><real>888</real>', 'testKey', 'string', 888,
346
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
347
                    . 'or value type does not match element type: CFPropertyList\CFNumber'],
348
            'Update number with bool' => ['<key>testKey</key><real>888</real>', 'testKey', true, 888,
349
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
350
                    . 'or value type does not match element type: CFPropertyList\CFNumber'],
351
            'Update number with null' => ['<key>testKey</key><real>888</real>', 'testKey', null, 888,
352
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
353
                    . 'or value type does not match element type: CFPropertyList\CFNumber'],
354
            'Update number with array' => ['<key>testKey</key><real>888</real>', 'testKey', ['testValue'], 888,
355
                    'Use update_element_array to update a collection.'],
356
            'Update date with string' => ['<key>testKey</key><date>1940-10-09T22:13:56Z</date>', 'testKey', 'string',
357
                    '1940-10-10T06:13:56+08:00',
358
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
359
                    . 'or value type does not match element type: CFPropertyList\CFDate'],
360
            'Update data with number' => ['<key>testKey</key><data>testData</data>', 'testKey', 789, 'testData',
361
                    'Invalid parameter value detected (Only string, number and boolean elements can be updated, '
362
                    . 'or value type does not match element type: CFPropertyList\CFData'],
363
        ];
364
    }
365
 
366
    /**
367
     * Data provider for expected JSON from PList.
368
     *
369
     * Examples extracted from requirements listed in SEB Config Key documents.
370
     * https://safeexambrowser.org/developer/seb-config-key.html
371
     *
372
     * 1. Date should be in ISO 8601 format.
373
     * 2. Data should be base 64 encoded.
374
     * 3. String should be UTF-8 encoded.
375
     * 4, 5, 6, 7. No requirements for bools, arrays or dicts.
376
     * 8. Empty dicts should not be included.
377
     * 9. JSON key ordering should be case insensitive, and use string ordering.
378
     * 10. URL forward slashes should not be escaped.
379
     *
380
     * @return array
381
     */
382
    public function json_data_provider(): array {
383
        $data = "blahblah";
384
        $base64data = base64_encode($data);
385
 
386
        return [
387
            'date' => ["<key>date</key><date>1940-10-09T22:13:56Z</date>", "{\"date\":\"1940-10-09T22:13:56+00:00\"}"],
388
            'data' => ["<key>data</key><data>$base64data</data>", "{\"data\":\"$base64data\"}"],
389
            'string' => ["<key>string</key><string>hello wörld</string>", "{\"string\":\"hello wörld\"}"],
390
            'string with 1 backslash' => ["<key>string</key><string>ws:\localhost</string>", "{\"string\":\"ws:\localhost\"}"],
391
            'string with 2 backslashes' => ["<key>string</key><string>ws:\\localhost</string>",
392
                    '{"string":"ws:\\localhost"}'],
393
            'string with 3 backslashes' => ["<key>string</key><string>ws:\\\localhost</string>",
394
                    '{"string":"ws:\\\localhost"}'],
395
            'string with 4 backslashes' => ["<key>string</key><string>ws:\\\\localhost</string>",
396
                    '{"string":"ws:\\\\localhost"}'],
397
            'string with 5 backslashes' => ["<key>string</key><string>ws:\\\\\localhost</string>",
398
                    '{"string":"ws:\\\\\localhost"}'],
399
            'bool' => ["<key>bool</key><true/>", "{\"bool\":true}"],
400
            'array' => ["<key>array</key><array><key>arraybool</key><false/><key>arraybool2</key><true/></array>"
401
                    , "{\"array\":[false,true]}"],
402
            'empty array' => ["<key>bool</key><true/><key>array</key><array/>"
403
                    , "{\"array\":[],\"bool\":true}"],
404
            'dict' => ["<key>dict</key><dict><key>dictbool</key><false/><key>dictbool2</key><true/></dict>"
405
                    , "{\"dict\":{\"dictbool\":false,\"dictbool2\":true}}"],
406
            'empty dict' => ["<key>bool</key><true/><key>emptydict</key><dict/>", "{\"bool\":true}"],
407
            'unordered elements' => ["<key>testKey</key>"
408
                    . "<string>testValue</string>"
409
                    . "<key>allowWLAN</key>"
410
                    . "<string>testValue2</string>"
411
                    . "<key>allowWlan</key>"
412
                    . "<string>testValue3</string>"
413
                    , "{\"allowWlan\":\"testValue3\",\"allowWLAN\":\"testValue2\",\"testKey\":\"testValue\"}"],
414
            'url' => ["<key>url</key><string>http://test.com</string>", "{\"url\":\"http://test.com\"}"],
415
            'assoc dict' => ["<key>dict</key><dict><key>banana</key><false/><key>apple</key><true/></dict>",
416
                    "{\"dict\":{\"apple\":true,\"banana\":false}}"],
417
            'seq array' => ["<key>array</key><array><key>1</key><false/><key>2</key><true/>
418
<key>3</key><true/><key>4</key><true/><key>5</key><true/><key>6</key><true/>
419
<key>7</key><true/><key>8</key><true/><key>9</key><true/><key>10</key><true/></array>",
420
                    "{\"array\":[false,true,true,true,true,true,true,true,true,true]}"],
421
        ];
422
    }
423
}