Commit | Line | Data |
---|---|---|
4ac88d6d PS |
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 | /** | |
9f53b0e9 EL |
18 | * Test xml_writer tests. |
19 | * | |
4ac88d6d | 20 | * @package core_backup |
9f53b0e9 | 21 | * @category test |
4ac88d6d PS |
22 | * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
24 | */ | |
25 | ||
9f53b0e9 EL |
26 | namespace core_backup; |
27 | ||
28 | use memory_xml_output; | |
29 | use xml_contenttransformer; | |
30 | use xml_output; | |
31 | use xml_writer; | |
32 | use xml_writer_exception; | |
33 | ||
4ac88d6d PS |
34 | defined('MOODLE_INTERNAL') || die(); |
35 | ||
36 | // Include all the needed stuff | |
37 | global $CFG; | |
38 | require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); | |
39 | require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); | |
40 | require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php'); | |
41 | require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php'); | |
42 | ||
43 | /** | |
9f53b0e9 EL |
44 | * Test xml_writer tests. |
45 | * | |
46 | * @package core_backup | |
47 | * @category test | |
48 | * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} | |
49 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
4ac88d6d | 50 | */ |
9f53b0e9 | 51 | class writer_test extends \basic_testcase { |
4ac88d6d PS |
52 | |
53 | /** | |
54 | * test xml_writer public methods | |
55 | */ | |
56 | function test_xml_writer_public_api() { | |
57 | global $CFG; | |
58 | // Instantiate xml_output | |
59 | $xo = new memory_xml_output(); | |
60 | $this->assertTrue($xo instanceof xml_output); | |
61 | ||
62 | // Instantiate xml_writer with null xml_output | |
63 | try { | |
64 | $xw = new mock_xml_writer(null); | |
65 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 66 | } catch (\Exception $e) { |
4ac88d6d PS |
67 | $this->assertTrue($e instanceof xml_writer_exception); |
68 | $this->assertEquals($e->errorcode, 'invalid_xml_output'); | |
69 | } | |
70 | ||
71 | // Instantiate xml_writer with wrong xml_output object | |
72 | try { | |
9f53b0e9 | 73 | $xw = new mock_xml_writer(new \stdClass()); |
4ac88d6d | 74 | $this->assertTrue(false, 'xml_writer_exception expected'); |
9f53b0e9 | 75 | } catch (\Exception $e) { |
4ac88d6d PS |
76 | $this->assertTrue($e instanceof xml_writer_exception); |
77 | $this->assertEquals($e->errorcode, 'invalid_xml_output'); | |
78 | } | |
79 | ||
80 | // Instantiate xml_writer with wrong xml_contenttransformer object | |
81 | try { | |
9f53b0e9 | 82 | $xw = new mock_xml_writer($xo, new \stdClass()); |
4ac88d6d | 83 | $this->assertTrue(false, 'xml_writer_exception expected'); |
9f53b0e9 | 84 | } catch (\Exception $e) { |
4ac88d6d PS |
85 | $this->assertTrue($e instanceof xml_writer_exception); |
86 | $this->assertEquals($e->errorcode, 'invalid_xml_contenttransformer'); | |
87 | } | |
88 | ||
89 | // Instantiate xml_writer and start it twice | |
90 | $xw = new mock_xml_writer($xo); | |
91 | $xw->start(); | |
92 | try { | |
93 | $xw->start(); | |
94 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 95 | } catch (\Exception $e) { |
4ac88d6d PS |
96 | $this->assertTrue($e instanceof xml_writer_exception); |
97 | $this->assertEquals($e->errorcode, 'xml_writer_already_started'); | |
98 | } | |
99 | ||
100 | // Instantiate xml_writer and stop it twice | |
101 | $xo = new memory_xml_output(); | |
102 | $xw = new mock_xml_writer($xo); | |
103 | $xw->start(); | |
104 | $xw->stop(); | |
105 | try { | |
106 | $xw->stop(); | |
107 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 108 | } catch (\Exception $e) { |
4ac88d6d PS |
109 | $this->assertTrue($e instanceof xml_writer_exception); |
110 | $this->assertEquals($e->errorcode, 'xml_writer_already_stopped'); | |
111 | } | |
112 | ||
113 | // Stop writer without starting it | |
114 | $xo = new memory_xml_output(); | |
115 | $xw = new mock_xml_writer($xo); | |
116 | try { | |
117 | $xw->stop(); | |
118 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 119 | } catch (\Exception $e) { |
4ac88d6d PS |
120 | $this->assertTrue($e instanceof xml_writer_exception); |
121 | $this->assertEquals($e->errorcode, 'xml_writer_not_started'); | |
122 | } | |
123 | ||
124 | // Start writer after stopping it | |
125 | $xo = new memory_xml_output(); | |
126 | $xw = new mock_xml_writer($xo); | |
127 | $xw->start(); | |
128 | $xw->stop(); | |
129 | try { | |
130 | $xw->start(); | |
131 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 132 | } catch (\Exception $e) { |
4ac88d6d PS |
133 | $this->assertTrue($e instanceof xml_writer_exception); |
134 | $this->assertEquals($e->errorcode, 'xml_writer_already_stopped'); | |
135 | } | |
136 | ||
137 | // Try to set prologue/schema after start | |
138 | $xo = new memory_xml_output(); | |
139 | $xw = new mock_xml_writer($xo); | |
140 | $xw->start(); | |
141 | try { | |
142 | $xw->set_nonamespace_schema('http://moodle.org'); | |
143 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 144 | } catch (\Exception $e) { |
4ac88d6d PS |
145 | $this->assertTrue($e instanceof xml_writer_exception); |
146 | $this->assertEquals($e->errorcode, 'xml_writer_already_started'); | |
147 | } | |
148 | try { | |
149 | $xw->set_prologue('sweet prologue'); | |
150 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 151 | } catch (\Exception $e) { |
4ac88d6d PS |
152 | $this->assertTrue($e instanceof xml_writer_exception); |
153 | $this->assertEquals($e->errorcode, 'xml_writer_already_started'); | |
154 | } | |
155 | ||
156 | // Instantiate properly with memory_xml_output, start and stop. | |
157 | // Must get default UTF-8 prologue | |
158 | $xo = new memory_xml_output(); | |
159 | $xw = new mock_xml_writer($xo); | |
160 | $xw->start(); | |
161 | $xw->stop(); | |
162 | $this->assertEquals($xo->get_allcontents(), $xw->get_default_prologue()); | |
163 | ||
164 | // Instantiate, set prologue and schema, put 1 full tag and get results | |
165 | $xo = new memory_xml_output(); | |
166 | $xw = new mock_xml_writer($xo); | |
167 | $xw->set_prologue('CLEARLY WRONG PROLOGUE'); | |
168 | $xw->set_nonamespace_schema('http://moodle.org/littleschema'); | |
169 | $xw->start(); | |
170 | $xw->full_tag('TEST', 'Hello World!', array('id' => 1)); | |
171 | $xw->stop(); | |
172 | $result = $xo->get_allcontents(); | |
173 | // Perform various checks | |
174 | $this->assertEquals(strpos($result, 'WRONG'), 8); | |
175 | $this->assertEquals(strpos($result, '<TEST id="1"'), 22); | |
176 | $this->assertEquals(strpos($result, 'xmlns:xsi='), 39); | |
177 | $this->assertEquals(strpos($result, 'http://moodle.org/littleschema'), 128); | |
178 | $this->assertEquals(strpos($result, 'Hello World'), 160); | |
179 | $this->assertFalse(strpos($result, $xw->get_default_prologue())); | |
180 | ||
181 | // Try to close one tag in wrong order | |
182 | $xo = new memory_xml_output(); | |
183 | $xw = new mock_xml_writer($xo); | |
184 | $xw->start(); | |
185 | $xw->begin_tag('first'); | |
186 | $xw->begin_tag('second'); | |
187 | try { | |
188 | $xw->end_tag('first'); | |
189 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 190 | } catch (\Exception $e) { |
4ac88d6d PS |
191 | $this->assertTrue($e instanceof xml_writer_exception); |
192 | $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match'); | |
193 | } | |
194 | ||
195 | // Try to close one tag before starting any tag | |
196 | $xo = new memory_xml_output(); | |
197 | $xw = new mock_xml_writer($xo); | |
198 | $xw->start(); | |
199 | try { | |
200 | $xw->end_tag('first'); | |
201 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 202 | } catch (\Exception $e) { |
4ac88d6d PS |
203 | $this->assertTrue($e instanceof xml_writer_exception); |
204 | $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match'); | |
205 | } | |
206 | ||
207 | // Full tag without contents (null and empty string) | |
208 | $xo = new memory_xml_output(); | |
209 | $xw = new mock_xml_writer($xo); | |
210 | $xw->set_prologue(''); // empty prologue for easier matching | |
211 | $xw->start(); | |
212 | $xw->full_tag('tagname', null, array('attrname' => 'attrvalue')); | |
213 | $xw->full_tag('tagname2', '', array('attrname' => 'attrvalue')); | |
214 | $xw->stop(); | |
215 | $result = $xo->get_allcontents(); | |
216 | $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue"></tagname2>'); | |
217 | ||
218 | ||
219 | // Test case-folding is working | |
220 | $xo = new memory_xml_output(); | |
221 | $xw = new mock_xml_writer($xo, null, true); | |
222 | $xw->set_prologue(''); // empty prologue for easier matching | |
223 | $xw->start(); | |
224 | $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attrvalue')); | |
225 | $xw->stop(); | |
226 | $result = $xo->get_allcontents(); | |
227 | $this->assertEquals($result, '<TAGNAME ATTRNAME="attrvalue">textcontent</TAGNAME>'); | |
228 | ||
229 | // Test UTF-8 chars in tag and attribute names, attr values and contents | |
230 | $xo = new memory_xml_output(); | |
231 | $xw = new mock_xml_writer($xo); | |
232 | $xw->set_prologue(''); // empty prologue for easier matching | |
233 | $xw->start(); | |
234 | $xw->full_tag('áéíóú', 'ÁÉÍÓÚ', array('àèìòù' => 'ÀÈÌÒÙ')); | |
235 | $xw->stop(); | |
236 | $result = $xo->get_allcontents(); | |
237 | $this->assertEquals($result, '<áéíóú àèìòù="ÀÈÌÒÙ">ÁÉÍÓÚ</áéíóú>'); | |
238 | ||
239 | // Try non-safe content in attributes | |
240 | $xo = new memory_xml_output(); | |
241 | $xw = new mock_xml_writer($xo); | |
242 | $xw->set_prologue(''); // empty prologue for easier matching | |
243 | $xw->start(); | |
244 | $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attr' . chr(27) . '\'"value')); | |
245 | $xw->stop(); | |
246 | $result = $xo->get_allcontents(); | |
247 | $this->assertEquals($result, '<tagname attrname="attr\'"value">textcontent</tagname>'); | |
248 | ||
249 | // Try non-safe content in text | |
250 | $xo = new memory_xml_output(); | |
251 | $xw = new mock_xml_writer($xo); | |
252 | $xw->set_prologue(''); // empty prologue for easier matching | |
253 | $xw->start(); | |
254 | $xw->full_tag('tagname', "text\r\ncontent\rwith" . chr(27), array('attrname' => 'attrvalue')); | |
255 | $xw->stop(); | |
256 | $result = $xo->get_allcontents(); | |
257 | $this->assertEquals($result, '<tagname attrname="attrvalue">text' . "\ncontent\n" . 'with</tagname>'); | |
258 | ||
259 | // Try to stop the writer without clossing all the open tags | |
260 | $xo = new memory_xml_output(); | |
261 | $xw = new mock_xml_writer($xo); | |
262 | $xw->start(); | |
263 | $xw->begin_tag('first'); | |
264 | try { | |
265 | $xw->stop(); | |
266 | $this->assertTrue(false, 'xml_writer_exception expected'); | |
9f53b0e9 | 267 | } catch (\Exception $e) { |
4ac88d6d PS |
268 | $this->assertTrue($e instanceof xml_writer_exception); |
269 | $this->assertEquals($e->errorcode, 'xml_writer_open_tags_remaining'); | |
270 | } | |
271 | ||
272 | // Test simple transformer | |
273 | $xo = new memory_xml_output(); | |
274 | $xt = new mock_xml_contenttransformer(); | |
275 | $xw = new mock_xml_writer($xo, $xt); | |
276 | $xw->set_prologue(''); // empty prologue for easier matching | |
277 | $xw->start(); | |
278 | $xw->full_tag('tagname', null, array('attrname' => 'attrvalue')); | |
279 | $xw->full_tag('tagname2', 'somecontent', array('attrname' => 'attrvalue')); | |
280 | $xw->stop(); | |
281 | $result = $xo->get_allcontents(); | |
282 | $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue">testsomecontent</tagname2>'); | |
283 | ||
284 | // Build a complex XML file and test results against stored file in fixtures | |
285 | $xo = new memory_xml_output(); | |
286 | $xw = new mock_xml_writer($xo); | |
287 | $xw->start(); | |
288 | $xw->begin_tag('toptag', array('name' => 'toptag', 'level' => 1, 'path' => '/toptag')); | |
289 | $xw->full_tag('secondtag', 'secondvalue', array('name' => 'secondtag', 'level' => 2, 'path' => '/toptag/secondtag', 'value' => 'secondvalue')); | |
290 | $xw->begin_tag('thirdtag', array('name' => 'thirdtag', 'level' => 2, 'path' => '/toptag/thirdtag')); | |
291 | $xw->full_tag('onevalue', 'onevalue', array('name' => 'onevalue', 'level' => 3, 'path' => '/toptag/thirdtag/onevalue')); | |
292 | $xw->full_tag('onevalue', 'anothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'anothervalue')); | |
293 | $xw->full_tag('onevalue', 'yetanothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'yetanothervalue')); | |
294 | $xw->full_tag('twovalue', 'twovalue', array('name' => 'twovalue', 'level' => 3, 'path' => '/toptag/thirdtag/twovalue')); | |
295 | $xw->begin_tag('forthtag', array('name' => 'forthtag', 'level' => 3, 'path' => '/toptag/thirdtag/forthtag')); | |
296 | $xw->full_tag('innervalue', 'innervalue'); | |
297 | $xw->begin_tag('innertag'); | |
298 | $xw->begin_tag('superinnertag', array('name' => 'superinnertag', 'level' => 5)); | |
299 | $xw->full_tag('superinnervalue', 'superinnervalue', array('name' => 'superinnervalue', 'level' => 6)); | |
300 | $xw->end_tag('superinnertag'); | |
301 | $xw->end_tag('innertag'); | |
302 | $xw->end_tag('forthtag'); | |
303 | $xw->begin_tag('fifthtag', array('level' => 3)); | |
304 | $xw->begin_tag('sixthtag', array('level' => 4)); | |
305 | $xw->full_tag('seventh', 'seventh', array('level' => 5)); | |
306 | $xw->end_tag('sixthtag'); | |
307 | $xw->end_tag('fifthtag'); | |
308 | $xw->full_tag('finalvalue', 'finalvalue', array('name' => 'finalvalue', 'level' => 3, 'path' => '/toptag/thirdtag/finalvalue')); | |
309 | $xw->full_tag('finalvalue'); | |
310 | $xw->end_tag('thirdtag'); | |
311 | $xw->end_tag('toptag'); | |
312 | $xw->stop(); | |
313 | $result = $xo->get_allcontents(); | |
bb7898c6 | 314 | $fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml'); |
4ac88d6d PS |
315 | |
316 | // Normalise carriage return characters. | |
317 | $fcontents = str_replace("\r\n", "\n", $fcontents); | |
318 | $this->assertEquals(trim($result), trim($fcontents)); | |
319 | } | |
320 | } | |
321 | ||
322 | /* | |
323 | * helper extended xml_writer class that makes some methods public for testing | |
324 | */ | |
325 | class mock_xml_writer extends xml_writer { | |
326 | public function get_default_prologue() { | |
327 | return parent::get_default_prologue(); | |
328 | } | |
329 | } | |
330 | ||
331 | /* | |
332 | * helper extended xml_contenttransformer prepending "test" to all the notnull contents | |
333 | */ | |
334 | class mock_xml_contenttransformer extends xml_contenttransformer { | |
335 | public function process($content) { | |
336 | return is_null($content) ? null : 'test' . $content; | |
337 | } | |
338 | } |