2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * HTTPS find and replace Tests
20 * @package tool_httpsreplace
21 * @copyright Copyright (c) 2016 Blackboard Inc. (http://www.blackboard.com)
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 namespace tool_httpsreplace\tests;
28 defined('MOODLE_INTERNAL') || die();
31 * Tests the httpsreplace tool.
33 * @package tool_httpsreplace
34 * @copyright Copyright (c) 2016 Blackboard Inc. (http://www.blackboard.com)
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class httpsreplace_test extends \advanced_testcase {
40 * Data provider for test_upgrade_http_links
42 public function upgrade_http_links_provider() {
44 // Get the http url, since the default test wwwroot is https.
45 $wwwroothttp = preg_replace('/^https:/', 'http:', $CFG->wwwroot);
47 "Test image from another site should be replaced" => [
48 "content" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', false) . '">',
49 "outputregex" => '/UPDATE/',
50 "expectedcontent" => '<img src="' . $this->get_converted_http_link('/test.jpg') . '">',
52 "Test object from another site should be replaced" => [
53 "content" => '<object data="' . $this->getExternalTestFileUrl('/test.swf', false) . '">',
54 "outputregex" => '/UPDATE/',
55 "expectedcontent" => '<object data="' . $this->get_converted_http_link('/test.swf') . '">',
57 "Test image from a site with international name should be replaced" => [
58 "content" => '<img src="http://中国互联网络信息中心.中国/logosy/201706/W01.png">',
59 "outputregex" => '/UPDATE/',
60 "expectedcontent" => '<img src="https://中国互联网络信息中心.中国/logosy/201706/W01.png">',
62 "Link that is from this site should be replaced" => [
63 "content" => '<img src="' . $wwwroothttp . '/logo.png">',
64 "outputregex" => '/UPDATE/',
65 "expectedcontent" => '<img src="' . $CFG->wwwroot . '/logo.png">',
67 "Link that is from this site, https new so doesn't need replacing" => [
68 "content" => '<img src="' . $CFG->wwwroot . '/logo.png">',
69 "outputregex" => '/^$/',
70 "expectedcontent" => '<img src="' . $CFG->wwwroot . '/logo.png">',
72 "Unavailable image should be replaced" => [
73 "content" => '<img src="http://intentionally.unavailable/link1.jpg">',
74 "outputregex" => '/UPDATE/',
75 "expectedcontent" => '<img src="https://intentionally.unavailable/link1.jpg">',
77 "Https content that has an http url as a param should not be replaced" => [
78 "content" => '<img src="https://anothersite.com?param=http://asdf.com">',
79 "outputregex" => '/^$/',
80 "expectedcontent" => '<img src="https://anothersite.com?param=http://asdf.com">',
82 "Search for params should be case insensitive" => [
83 "content" => '<object DATA="' . $this->getExternalTestFileUrl('/test.swf', false) . '">',
84 "outputregex" => '/UPDATE/',
85 "expectedcontent" => '<object DATA="' . $this->get_converted_http_link('/test.swf') . '">',
87 "URL should be case insensitive" => [
88 "content" => '<object data="HTTP://some.site/path?query">',
89 "outputregex" => '/UPDATE/',
90 "expectedcontent" => '<object data="https://some.site/path?query">',
92 "More params should not interfere" => [
93 "content" => '<img alt="A picture" src="' . $this->getExternalTestFileUrl('/test.png', false) .
94 '" width="1”><p style="font-size: \'20px\'"></p>',
95 "outputregex" => '/UPDATE/',
96 "expectedcontent" => '<img alt="A picture" src="' . $this->get_converted_http_link('/test.png') .
97 '" width="1”><p style="font-size: \'20px\'"></p>',
99 "Broken URL should not be changed" => [
100 "content" => '<img src="broken.' . $this->getExternalTestFileUrl('/test.png', false) . '">',
101 "outputregex" => '/^$/',
102 "expectedcontent" => '<img src="broken.' . $this->getExternalTestFileUrl('/test.png', false) . '">',
104 "Link URL should not be changed" => [
105 "content" => '<a href="' . $this->getExternalTestFileUrl('/test.png', false) . '">' .
106 $this->getExternalTestFileUrl('/test.png', false) . '</a>',
107 "outputregex" => '/^$/',
108 "expectedcontent" => '<a href="' . $this->getExternalTestFileUrl('/test.png', false) . '">' .
109 $this->getExternalTestFileUrl('/test.png', false) . '</a>',
111 "Test image from another site should be replaced but link should not" => [
112 "content" => '<a href="' . $this->getExternalTestFileUrl('/test.png', false) . '"><img src="' .
113 $this->getExternalTestFileUrl('/test.jpg', false) . '"></a>',
114 "outputregex" => '/UPDATE/',
115 "expectedcontent" => '<a href="' . $this->getExternalTestFileUrl('/test.png', false) . '"><img src="' .
116 $this->get_converted_http_link('/test.jpg') . '"></a>',
122 * Convert the HTTP external test file URL to use HTTPS.
124 * Note: We *must not* use getExternalTestFileUrl with the True option
125 * here, becase it is reasonable to have only one of these set due to
126 * issues with SSL certificates.
128 * @param string $path Path to be rewritten
131 protected function get_converted_http_link($path) {
132 return preg_replace('/^http:/', 'https:', $this->getExternalTestFileUrl($path, false));
136 * Test upgrade_http_links
137 * @param string $content Example content that we'll attempt to replace.
138 * @param string $ouputregex Regex for what output we expect.
139 * @param string $expectedcontent What content we are expecting afterwards.
140 * @dataProvider upgrade_http_links_provider
142 public function test_upgrade_http_links($content, $ouputregex, $expectedcontent) {
145 $this->resetAfterTest();
146 $this->expectOutputRegex($ouputregex);
148 $finder = new tool_httpreplace_url_finder_test();
150 $generator = $this->getDataGenerator();
151 $course = $generator->create_course((object) [
152 'summary' => $content,
155 $finder->upgrade_http_links();
157 $summary = $DB->get_field('course', 'summary', ['id' => $course->id]);
158 $this->assertStringContainsString($expectedcontent, $summary);
162 * Data provider for test_http_link_stats
164 public function http_link_stats_provider() {
166 // Get the http url, since the default test wwwroot is https.
167 $wwwrootdomain = 'www.example.com';
168 $wwwroothttp = preg_replace('/^https:/', 'http:', $CFG->wwwroot);
169 $testdomain = $this->get_converted_http_link('');
171 "Test image from an available site so shouldn't be reported" => [
172 "content" => '<img src="' . $this->getExternalTestFileUrl('/test.jpg', false) . '">',
173 "domain" => $testdomain,
174 "expectedcount" => 0,
176 "Link that is from this site shouldn't be reported" => [
177 "content" => '<img src="' . $wwwroothttp . '/logo.png">',
178 "domain" => $wwwrootdomain,
179 "expectedcount" => 0,
181 "Unavailable, but https shouldn't be reported" => [
182 "content" => '<img src="https://intentionally.unavailable/logo.png">',
183 "domain" => 'intentionally.unavailable',
184 "expectedcount" => 0,
186 "Unavailable image should be reported" => [
187 "content" => '<img src="http://intentionally.unavailable/link1.jpg">',
188 "domain" => 'intentionally.unavailable',
189 "expectedcount" => 1,
191 "Unavailable object should be reported" => [
192 "content" => '<object data="http://intentionally.unavailable/file.swf">',
193 "domain" => 'intentionally.unavailable',
194 "expectedcount" => 1,
196 "Link should not be reported" => [
197 "content" => '<a href="http://intentionally.unavailable/page.php">Link</a>',
198 "domain" => 'intentionally.unavailable',
199 "expectedcount" => 0,
201 "Text should not be reported" => [
202 "content" => 'http://intentionally.unavailable/page.php',
203 "domain" => 'intentionally.unavailable',
204 "expectedcount" => 0,
210 * Test http_link_stats
211 * @param string $content Example content that we'll attempt to replace.
212 * @param string $domain The domain we will check was replaced.
213 * @param string $expectedcount Number of urls from that domain that we expect to be replaced.
214 * @dataProvider http_link_stats_provider
216 public function test_http_link_stats($content, $domain, $expectedcount) {
217 $this->resetAfterTest();
219 $finder = new tool_httpreplace_url_finder_test();
221 $generator = $this->getDataGenerator();
222 $course = $generator->create_course((object) [
223 'summary' => $content,
226 $results = $finder->http_link_stats();
228 $this->assertEquals($expectedcount, $results[$domain] ?? 0);
232 * Test links and text are not changed
234 public function test_links_and_text() {
237 $this->resetAfterTest();
238 $this->expectOutputRegex('/^$/');
240 $finder = new tool_httpreplace_url_finder_test();
242 $generator = $this->getDataGenerator();
243 $course = $generator->create_course((object) [
244 'summary' => '<a href="http://intentionally.unavailable/page.php">Link</a> http://other.unavailable/page.php',
247 $results = $finder->http_link_stats();
248 $this->assertCount(0, $results);
250 $finder->upgrade_http_links();
252 $results = $finder->http_link_stats();
253 $this->assertCount(0, $results);
255 $summary = $DB->get_field('course', 'summary', ['id' => $course->id]);
256 $this->assertStringContainsString('http://intentionally.unavailable/page.php', $summary);
257 $this->assertStringContainsString('http://other.unavailable/page.php', $summary);
258 $this->assertStringNotContainsString('https://intentionally.unavailable', $summary);
259 $this->assertStringNotContainsString('https://other.unavailable', $summary);
263 * If we have an http wwwroot then we shouldn't report it.
265 public function test_httpwwwroot() {
268 $this->resetAfterTest();
269 $CFG->wwwroot = preg_replace('/^https:/', 'http:', $CFG->wwwroot);
270 $this->expectOutputRegex('/^$/');
272 $finder = new tool_httpreplace_url_finder_test();
274 $generator = $this->getDataGenerator();
275 $course = $generator->create_course((object) [
276 'summary' => '<img src="' . $CFG->wwwroot . '/image.png">',
279 $results = $finder->http_link_stats();
280 $this->assertCount(0, $results);
282 $finder->upgrade_http_links();
283 $summary = $DB->get_field('course', 'summary', ['id' => $course->id]);
284 $this->assertStringContainsString($CFG->wwwroot, $summary);
288 * Test that links in excluded tables are not replaced
290 public function test_upgrade_http_links_excluded_tables() {
291 $this->resetAfterTest();
293 set_config('test_upgrade_http_links', '<img src="http://somesite/someimage.png" />');
295 $finder = new tool_httpreplace_url_finder_test();
297 $results = $finder->upgrade_http_links();
298 $output = ob_get_contents();
300 $this->assertTrue($results);
301 $this->assertStringNotContainsString('https://somesite', $output);
302 $testconf = get_config('core', 'test_upgrade_http_links');
303 $this->assertStringContainsString('http://somesite', $testconf);
304 $this->assertStringNotContainsString('https://somesite', $testconf);
308 * Test renamed domains
310 public function test_renames() {
312 $this->resetAfterTest();
313 $this->expectOutputRegex('/UPDATE/');
316 'example.com' => 'secure.example.com',
319 set_config('renames', json_encode($renames), 'tool_httpsreplace');
321 $finder = new tool_httpreplace_url_finder_test();
323 $generator = $this->getDataGenerator();
324 $course = $generator->create_course((object) [
325 'summary' => '<script src="http://example.com/test.js"><img src="http://EXAMPLE.COM/someimage.png">',
328 $results = $finder->http_link_stats();
329 $this->assertCount(0, $results);
331 $finder->upgrade_http_links();
333 $summary = $DB->get_field('course', 'summary', ['id' => $course->id]);
334 $this->assertStringContainsString('https://secure.example.com', $summary);
335 $this->assertStringNotContainsString('http://example.com', $summary);
336 $this->assertEquals('<script src="https://secure.example.com/test.js">' .
337 '<img src="https://secure.example.com/someimage.png">', $summary);
341 * When there are many different pieces of contents from the same site, we should only run replace once
343 public function test_multiple() {
345 $this->resetAfterTest();
350 for ($i = 0; $i < 15; $i++) {
351 $original1 .= '<img src="http://example.com/image' . $i . '.png">';
352 $expected1 .= '<img src="https://example.com/image' . $i . '.png">';
353 $original2 .= '<img src="http://example.com/image' . ($i + 15 ) . '.png">';
354 $expected2 .= '<img src="https://example.com/image' . ($i + 15) . '.png">';
356 $finder = new tool_httpreplace_url_finder_test();
358 $generator = $this->getDataGenerator();
359 $course1 = $generator->create_course((object) ['summary' => $original1]);
360 $course2 = $generator->create_course((object) ['summary' => $original2]);
363 $finder->upgrade_http_links();
364 $output = ob_get_contents();
367 // Make sure everything is replaced.
368 $summary1 = $DB->get_field('course', 'summary', ['id' => $course1->id]);
369 $this->assertEquals($expected1, $summary1);
370 $summary2 = $DB->get_field('course', 'summary', ['id' => $course2->id]);
371 $this->assertEquals($expected2, $summary2);
373 // Make sure only one UPDATE statment was called.
374 $this->assertEquals(1, preg_match_all('/UPDATE/', $output));
378 * Test the tool when the column name is a reserved word in SQL (in this case 'where')
380 public function test_reserved_words() {
383 $this->resetAfterTest();
384 $this->expectOutputRegex('/UPDATE/');
386 // Create a table with a field that is a reserved SQL word.
387 $dbman = $DB->get_manager();
388 $table = new \xmldb_table('reserved_words_temp');
389 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
390 $table->add_field('where', XMLDB_TYPE_TEXT, null, null, null, null, null);
391 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
392 $dbman->create_table($table);
394 // Insert a record with an <img> in this table and run tool.
395 $content = '<img src="http://example.com/image.png">';
396 $expectedcontent = '<img src="https://example.com/image.png">';
397 $columnamequoted = $dbman->generator->getEncQuoted('where');
398 $DB->execute("INSERT INTO {reserved_words_temp} ($columnamequoted) VALUES (?)", [$content]);
400 $finder = new tool_httpreplace_url_finder_test();
401 $finder->upgrade_http_links();
403 $record = $DB->get_record('reserved_words_temp', []);
404 $this->assertStringContainsString($expectedcontent, $record->where);
406 $dbman->drop_table($table);
411 * Class tool_httpreplace_url_finder_test for testing replace tool without calling curl
413 * @package tool_httpsreplace
414 * @copyright 2017 Marina Glancy
415 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
417 class tool_httpreplace_url_finder_test extends \tool_httpsreplace\url_finder {
419 * Check if url is available (check hardcoded for unittests)
424 protected function check_domain_availability($url) {
425 return !preg_match('|\.unavailable/$|', $url);