cdn.test

Test CDN.

Classes

File

modules/contrib/cdn/cdn.test
View source
  1. <?php
  2. /**
  3. * @file
  4. * Test CDN.
  5. */
  6. class CDNTestCase extends DrupalUnitTestCase {
  7. function setUp() {
  8. parent::setUp();
  9. // Alter $_SERVER to include some relatively rarely set HTTP headers.
  10. $alt_server = array(
  11. 'HTTP_ACCEPT_ENCODING',
  12. 'HTTPS' => 'off',
  13. 'HTTP_X_FORWARDED_PROTO' => 'http',
  14. );
  15. $alt_server = array_merge($alt_server, $_SERVER);
  16. $_SERVER = $alt_server;
  17. $this->setRequestProtocol('http');
  18. // Pretend the CDN module is enabled; this ensures invocations of its own
  19. // hook implementations will work as expected.
  20. $cdn_module_file = drupal_get_path('module', 'cdn') . '/cdn.module';
  21. $module_list = module_list();
  22. $module_list['cdn']['filename'] = $cdn_module_file;
  23. module_list(TRUE, FALSE, FALSE, $module_list);
  24. $implementations = &drupal_static('module_implements');
  25. $implementations = array();
  26. $this->loadFile('cdn.constants.inc');
  27. $this->loadFile('cdn.module');
  28. // Override $conf to be able to use variable_set() and variable_get() in
  29. // DrupalUnitTestCase. At the same time, make sure we can restore the
  30. // original values.
  31. global $conf;
  32. $this->originalConfig = $conf;
  33. $this->variableSetDefaults();
  34. }
  35. function tearDown() {
  36. // Restore the original values that are used by variable_get().
  37. global $conf;
  38. $conf = $this->originalConfig;
  39. parent::tearDown();
  40. }
  41. function loadFile($file) {
  42. $cdn_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'cdn');
  43. require_once "$cdn_path/$file";
  44. }
  45. /**
  46. * Mock function for variable_set().
  47. */
  48. function variableSet($name, $value) {
  49. global $conf;
  50. $conf[$name] = $value;
  51. }
  52. /**
  53. * Set the default variable values for the CDN module.
  54. */
  55. function variableSetDefaults() {
  56. global $conf;
  57. $this->defaultConfig = array(
  58. CDN_STATUS_VARIABLE => CDN_ENABLED,
  59. CDN_MODE_VARIABLE => FALSE,
  60. CDN_HTTPS_SUPPORT_VARIABLE => FALSE,
  61. CDN_BASIC_MAPPING_VARIABLE => '',
  62. CDN_BASIC_MAPPING_HTTPS_VARIABLE => '',
  63. CDN_BASIC_FARFUTURE_VARIABLE => FALSE,
  64. );
  65. $conf = array_merge($conf, $this->defaultConfig);
  66. }
  67. /**
  68. * Set the protocol of the current "request".
  69. *
  70. * @param $protocol
  71. * 'http' or 'https'.
  72. */
  73. function setRequestProtocol($protocol) {
  74. if ($protocol == 'http') {
  75. $_SERVER['HTTPS'] = 'off';
  76. $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
  77. }
  78. elseif ($protocol == 'https') {
  79. $_SERVER['HTTPS'] = 'on';
  80. $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
  81. }
  82. }
  83. /**
  84. * Configure HTTPS-related settings.
  85. *
  86. * @param $supported
  87. * Boolean that indicates whether HTTPS is supported by the current CDN
  88. * or not.
  89. * @param $mapping
  90. * The CDN mapping to use when the CDN supports HTTPS and the current
  91. * request is happening over HTTPS.
  92. */
  93. function configureHTTPS($supported, $mapping = '') {
  94. $this->variableSet(CDN_HTTPS_SUPPORT_VARIABLE, $supported);
  95. $this->variableSet(CDN_BASIC_MAPPING_HTTPS_VARIABLE, $mapping);
  96. }
  97. /**
  98. * Given a file URI, get the expanded file path.
  99. *
  100. * @param $uri
  101. * @see file_stream_wrapper_get_instance_by_uri()
  102. * @return
  103. * A Drupal root-relative path.
  104. */
  105. function getExpandedFilePath($uri) {
  106. $wrapper = file_stream_wrapper_get_instance_by_uri($uri);
  107. return str_replace($GLOBALS['base_url'] . '/', '', $wrapper->getExternalUrl());
  108. }
  109. /**
  110. * Given a file URI, get its path, create the file and ensure it exists.
  111. *
  112. * @param $uri
  113. * @see getExpandedFilePath()
  114. */
  115. function touchFile($uri) {
  116. $path = $this->getExpandedFilePath($uri);
  117. $this->assertTrue(touch(rawurldecode($path)), 'Test file created.');
  118. return $path;
  119. }
  120. }
  121. class CDNGeneralTestCase extends CDNTestCase {
  122. public static function getInfo() {
  123. return array(
  124. 'name' => 'General',
  125. 'description' => 'Verify general functionality.',
  126. 'group' => 'CDN',
  127. );
  128. }
  129. function testHTTPSDetection() {
  130. // HTTPS + HTTP_X_FORWARDED_PROTO permutations.
  131. $_SERVER['HTTPS'] = 'off';
  132. $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
  133. $this->assertEqual(FALSE, cdn_request_is_https(), 'HTTP request detected.');
  134. $_SERVER['HTTPS'] = 'off';
  135. $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
  136. $this->assertEqual(TRUE, cdn_request_is_https(), 'HTTPS request detected.');
  137. $_SERVER['HTTPS'] = 'on';
  138. $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
  139. $this->assertEqual(TRUE, cdn_request_is_https(), 'HTTPS request detected.');
  140. $_SERVER['HTTPS'] = 'on';
  141. $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
  142. $this->assertEqual(TRUE, cdn_request_is_https(), 'HTTPS request detected.');
  143. // HTTPS + HTTP_X_FORWARDED_PROTOCOL permutations.
  144. unset($_SERVER['HTTP_X_FORWARDED_PROTO']);
  145. $_SERVER['HTTPS'] = 'off';
  146. $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
  147. $this->assertEqual(FALSE, cdn_request_is_https(), 'HTTP request detected.');
  148. $_SERVER['HTTPS'] = 'off';
  149. $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
  150. $this->assertEqual(TRUE, cdn_request_is_https(), 'HTTPS request detected.');
  151. $_SERVER['HTTPS'] = 'on';
  152. $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
  153. $this->assertEqual(TRUE, cdn_request_is_https(), 'HTTPS request detected.');
  154. $_SERVER['HTTPS'] = 'on';
  155. $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
  156. $this->assertEqual(TRUE, cdn_request_is_https(), 'HTTPS request detected.');
  157. }
  158. }
  159. class CDNOriginPullTestCase extends CDNTestCase {
  160. public static function getInfo() {
  161. return array(
  162. 'name' => 'Origin Pull mode',
  163. 'description' => 'Verify Origin Pull mode-related functionality.',
  164. 'group' => 'CDN',
  165. );
  166. }
  167. function setUp() {
  168. parent::setUp();
  169. $this->loadFile('cdn.basic.inc');
  170. $this->loadFile('cdn.basic.farfuture.inc');
  171. $this->variableSet(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
  172. }
  173. /**
  174. * Assert a CDN mapping (and optionally set a mapping).
  175. *
  176. * @param $mapping
  177. * The mapping to set; if FALSE, no new mapping will be set.
  178. * @param $parsed_reference
  179. * The reference the parsed mapping will be compared to.
  180. * @param $domains_reference
  181. * The reference the domains (as returned by cdn_get_domains()) will be
  182. * compared to.
  183. */
  184. function assertMapping($mapping, $parsed_reference, $domains_reference) {
  185. if ($mapping !== FALSE) {
  186. $this->variableSet(CDN_BASIC_MAPPING_VARIABLE, $mapping);
  187. }
  188. $this->assertEqual($parsed_reference, _cdn_basic_parse_raw_mapping(cdn_basic_get_mapping()), 'CDN mapping parsed correctly.');
  189. $domains = cdn_get_domains();
  190. sort($domains);
  191. $this->assertEqual($domains_reference, $domains, 'CDN domains parsed correctly.');
  192. }
  193. function testMapping() {
  194. $this->setRequestProtocol('http');
  195. $this->assertEqual('', cdn_basic_get_mapping(), 'The default CDN mapping is empty.');
  196. // Ensure the parsing of the raw mapping works correctly.
  197. $this->assertMapping('', array(), array());
  198. $this->assertMapping('http://cdn-a.com', array('*' => array('http://cdn-a.com')), array('cdn-a.com'));
  199. $this->assertMapping('http://cdn-a.com/', array('*' => array('http://cdn-a.com')), array('cdn-a.com'));
  200. $this->assertMapping('//cdn-a.com', array('*' => array('//cdn-a.com')), array('cdn-a.com'));
  201. $this->assertMapping('//cdn-a.com/', array('*' => array('//cdn-a.com')), array('cdn-a.com'));
  202. $parsed_mapping = array(
  203. 'css' => array('http://cdn-a.com'),
  204. 'jpg' => array('http://cdn-a.com'),
  205. 'jpeg' => array('http://cdn-a.com'),
  206. 'png' => array('http://cdn-a.com'),
  207. 'zip' => array('http://cdn-b.com'),
  208. '*' => array('http://cdn-c.com'),
  209. );
  210. $domains = array('cdn-a.com', 'cdn-b.com', 'cdn-c.com');
  211. $this->assertMapping(
  212. "http://cdn-a.com|.css .jpg .jpeg .png\nhttp://cdn-b.com|.zip\nhttp://cdn-c.com",
  213. $parsed_mapping,
  214. $domains
  215. );
  216. $parsed_mapping = array(
  217. 'css' => array('http://cdn-a.com', 'http://cdn-d.com'),
  218. 'jpg' => array('http://cdn-a.com', 'http://cdn-d.com'),
  219. 'jpeg' => array('http://cdn-a.com', 'http://cdn-d.com'),
  220. 'png' => array('http://cdn-a.com', 'http://cdn-d.com', 'http://cdn-b.com'),
  221. '*' => array('http://cdn-c.com'),
  222. );
  223. $domains = array('cdn-a.com', 'cdn-b.com', 'cdn-c.com', 'cdn-d.com');
  224. $this->assertMapping(
  225. "http://cdn-a.com http://cdn-d.com|.css .jpg .jpeg .png\nhttp://cdn-b.com|.png\nhttp://cdn-c.com",
  226. $parsed_mapping,
  227. $domains
  228. );
  229. // When a HTTPS request is performed and the CDN is not marked to support
  230. // HTTPS, then it should fall back to the default CDN mapping.
  231. $this->setRequestProtocol('https');
  232. $this->assertMapping(FALSE, $parsed_mapping, $domains);
  233. // When a HTTPS request is performed and the CDN *is* marked to support
  234. // HTTPS, then it should still fall back to the default CDN mapping. (When
  235. // file URLs are actually altered, it will then replace `http://` with
  236. // `https://` -- this will be tested in a different test.)
  237. $this->configureHTTPS(TRUE);
  238. $this->assertMapping(FALSE, $parsed_mapping, $domains);
  239. // When a HTTPS request is performed *and* the CDN is marked to support
  240. // HTTPS *and* there's a HTTPS-specific CDN mapping, that mapping should
  241. // be used instead!
  242. $this->configureHTTPS(TRUE, "https://cdn-a.com|.css .jpg .jpeg .png\nhttps://cdn-b.com|.zip\nhttps://cdn-c.com");
  243. $this->assertMapping(
  244. FALSE,
  245. array(
  246. 'css' => array('https://cdn-a.com'),
  247. 'jpg' => array('https://cdn-a.com'),
  248. 'jpeg' => array('https://cdn-a.com'),
  249. 'png' => array('https://cdn-a.com'),
  250. 'zip' => array('https://cdn-b.com'),
  251. '*' => array('https://cdn-c.com'),
  252. ),
  253. array('cdn-a.com', 'cdn-b.com', 'cdn-c.com')
  254. );
  255. // Ensure the default CDN mapping is used whenever a HTTP request occurs
  256. // and the CDN is marked to suppport HTTPS and there's a HTTPS-specific
  257. // CDN mapping.
  258. $this->configureHTTPS(FALSE);
  259. $this->assertMapping(FALSE, $parsed_mapping, $domains);
  260. }
  261. function testFileUrlAlterHook() {
  262. // Provide a very basic CDN mapping.
  263. $this->variableSet(CDN_BASIC_MAPPING_VARIABLE, 'http://cdn-a.com');
  264. $filename = 'újjáépítésérol — 100% in B&W.jpg';
  265. $uri = "public://$filename";
  266. $this->touchFile($uri);
  267. cdn_file_url_alter($uri);
  268. $expected = 'http://cdn-a.com' . base_path() . variable_get('file_public_path', conf_path() . '/files') . '/' . drupal_encode_path($filename);
  269. $this->assertIdentical($uri, $expected, 'cdn_file_url_alter() works correctly.');
  270. }
  271. }
  272. class CDNOriginPullFarFutureTestCase extends CDNTestCase {
  273. public static function getInfo() {
  274. return array(
  275. 'name' => 'Origin Pull mode — Far Future expiration',
  276. 'description' => 'Verify Origin Pull mode\'s Far Future expiration functionality.',
  277. 'group' => 'CDN',
  278. );
  279. }
  280. function setUp() {
  281. parent::setUp();
  282. $this->loadFile('cdn.basic.inc');
  283. $this->loadFile('cdn.basic.farfuture.inc');
  284. $this->variableSet(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
  285. $this->variableSet(CDN_BASIC_FARFUTURE_VARIABLE, TRUE);
  286. }
  287. /**
  288. * Assert a UFI mapping (and optionally set a mapping).
  289. *
  290. * @param $mapping
  291. * The mapping to set; if FALSE, no new mapping will be set.
  292. * @param $parsed_reference
  293. * The reference the parsed mapping will be compared to.
  294. * @param $message
  295. */
  296. function assertUFIMapping($mapping, $parsed_reference, $message = NULL) {
  297. if (is_null($message)) {
  298. $message = 'UFI mapping parsed correctly.';
  299. }
  300. if ($mapping !== FALSE) {
  301. $this->variableSet(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, $mapping);
  302. }
  303. $parsed = _cdn_basic_farfuture_parse_raw_mapping(variable_get(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT));
  304. $this->assertEqual($parsed_reference, $parsed, $message);
  305. }
  306. /**
  307. * Assert a UFI method. Must be run after a UFI mapping is asserted (and
  308. * set) by assertUFIMapping().
  309. *
  310. * @param $path
  311. * The path to get a UFI for.
  312. * @param $ufi_method_reference
  313. * The reference the resulting UFI method will be compared to.
  314. */
  315. function assertUFIMethod($path, $ufi_method_reference) {
  316. $ufi_mapping = _cdn_basic_farfuture_parse_raw_mapping(variable_get(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT));
  317. $this->assertEqual(
  318. $ufi_method_reference,
  319. cdn_basic_farfuture_get_ufi_method($path, $ufi_mapping),
  320. 'Correct UFI method applied.'
  321. );
  322. }
  323. function testUFIMapping() {
  324. $default = CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_DEFAULT;
  325. $parsed_mapping = _cdn_basic_farfuture_parse_raw_mapping($default);
  326. $this->assertUFIMapping(
  327. FALSE,
  328. $parsed_mapping,
  329. 'The default UFI mapping is set to sensible defaults.'
  330. );
  331. // Growing complexity for a single-directory UFI.
  332. $this->assertUFIMapping(
  333. "sites/*|mtime",
  334. array(
  335. 'sites/*' => array(
  336. '*' => array(
  337. 'ufi method' => 'mtime',
  338. 'specificity' => 20, // two directories (2*10), no extension (0)
  339. ),
  340. ),
  341. ),
  342. 'Simple single-directory UFI mapping (step 1).'
  343. );
  344. $this->assertUFIMethod('sites/foo', 'mtime');
  345. $this->assertUFIMapping(
  346. "sites/*|mtime\nsites/*|.woff .ttf|md5_hash",
  347. array(
  348. 'sites/*' => array(
  349. '*' => array(
  350. 'ufi method' => 'mtime',
  351. 'specificity' => 20, // two directories (2*10), no extension (0)
  352. ),
  353. 'woff' => array(
  354. 'ufi method' => 'md5_hash',
  355. 'specificity' => 21, // two directories (2*10), one extension (1)
  356. ),
  357. 'ttf' => array(
  358. 'ufi method' => 'md5_hash',
  359. 'specificity' => 21, // two directories (2*10), one extension (1)
  360. ),
  361. ),
  362. ),
  363. 'Simple single-directory UFI mapping (step 2).'
  364. );
  365. $this->assertUFIMethod('sites/foo', 'mtime');
  366. $this->assertUFIMethod('sites/foobambamb.woff', 'md5_hash');
  367. $this->assertUFIMethod('sites/foo/bar/baz.ttf', 'md5_hash');
  368. $this->assertUFIMapping(
  369. "sites/*|mtime\nsites/*|.woff .ttf|md5_hash\nsites/*|.mov .mp4|perpetual",
  370. array(
  371. 'sites/*' => array(
  372. '*' => array(
  373. 'ufi method' => 'mtime',
  374. 'specificity' => 20, // two directories (2*10), no extension (0)
  375. ),
  376. 'woff' => array(
  377. 'ufi method' => 'md5_hash',
  378. 'specificity' => 21, // two directories (2*10), one extension (1)
  379. ),
  380. 'ttf' => array(
  381. 'ufi method' => 'md5_hash',
  382. 'specificity' => 21, // two directories (2*10), one extension (1)
  383. ),
  384. 'mov' => array(
  385. 'ufi method' => 'perpetual',
  386. 'specificity' => 21, // two directories (2*10), one extension (1)
  387. ),
  388. 'mp4' => array(
  389. 'ufi method' => 'perpetual',
  390. 'specificity' => 21, // two directories (2*10), one extension (1)
  391. ),
  392. ),
  393. ),
  394. 'Simple single-directory UFI mapping (step 2).'
  395. );
  396. $this->assertUFIMethod('sites/foo', 'mtime');
  397. $this->assertUFIMethod('sites/foobambamb.woff', 'md5_hash');
  398. $this->assertUFIMethod('sites/foo/bar/baz.ttf', 'md5_hash');
  399. $this->assertUFIMethod('sites/movies/foo.mov', 'perpetual');
  400. $this->assertUFIMethod('sites/movies/bar.mp4', 'perpetual');
  401. }
  402. function testFileUrlAlterHook() {
  403. // We don't want to test the UFI functionality here.
  404. $this->variableSet(CDN_BASIC_FARFUTURE_UNIQUE_IDENTIFIER_MAPPING_VARIABLE, '*|perpetual');
  405. // Provide a very basic CDN mapping.
  406. $this->variableSet(CDN_BASIC_MAPPING_VARIABLE, 'http://cdn-a.com');
  407. $filename = 'újjáépítésérol — 100% in B&W.jpg';
  408. $uri = "public://$filename";
  409. $this->touchFile($uri);
  410. cdn_file_url_alter($uri);
  411. $path_info = pathinfo($filename);
  412. $expected = implode('/', array(
  413. 'http://cdn-a.com' . base_path() . 'cdn/farfuture',
  414. drupal_hmac_base64('perpetual:forever' . $path_info['filename'], drupal_get_private_key() . drupal_get_hash_salt()),
  415. 'perpetual:forever',
  416. variable_get('file_public_path', conf_path() . '/files'),
  417. drupal_encode_path($filename)
  418. ));
  419. $this->assertIdentical($uri, $expected, 'cdn_file_url_alter() works correctly.');
  420. }
  421. }
  422. class CDNImageTestCase extends CDNTestCase {
  423. public static function getInfo() {
  424. return array(
  425. 'name' => 'Image HTML altering',
  426. 'description' => 'Verify that image URLs inside HTML are rewritten correctly.',
  427. 'group' => 'CDN',
  428. );
  429. }
  430. function setUp() {
  431. parent::setUp();
  432. $this->loadFile('cdn.fallback.inc');
  433. }
  434. function testImage() {
  435. $cdn = 'http://cdn-a.com';
  436. $this->variableSet(CDN_BASIC_MAPPING_VARIABLE, $cdn);
  437. $this->variableSet(CDN_MODE_VARIABLE, CDN_MODE_BASIC);
  438. // Image altering type 1: "just image", i.e. "<img />".
  439. $template = function($url) {
  440. return '<img src="' . $url . '" />';
  441. };
  442. // Simplest case possible.
  443. $img_url = base_path() . 'foo/bar/image.png';
  444. $html = $template($img_url);
  445. cdn_html_alter_image_urls($html);
  446. $this->assertIdentical($template($cdn . $img_url), $html, 'Image HTML correctly altered.');
  447. // Query strings should not be stripped
  448. $img_url = base_path() . 'foo/bar/image.png?foobar';
  449. $html = $template($img_url);
  450. cdn_html_alter_image_urls($html);
  451. $this->assertIdentical($template($cdn . $img_url), $html, 'Image HTML correctly altered (query string not stripped).');
  452. // In particular: not the query string used to generate image styles.
  453. $img_url = base_path() . 'foo/bar/image.png?itok=1234abcd';
  454. $html = $template($img_url);
  455. cdn_html_alter_image_urls($html);
  456. $this->assertIdentical($template($cdn . $img_url), $html, 'Image HTML correctly altered (image style query string not stripped).');
  457. // Edge case: a script generating an image is not (yet) supported.
  458. $img_url = base_path() . 'foo/bar/showimage?formula=12345.png';
  459. $html = $template($img_url);
  460. cdn_html_alter_image_urls($html);
  461. $this->assertIdentical($template($cdn . $img_url), $html, 'Image HTML correctly altered (query string not stripped).');
  462. // Image altering type 2: "linked image", i.e. "<a><img /></a>"..
  463. $template = function($a_url, $img_url) {
  464. return '<a href="' . $a_url . '"><img src="' . $img_url . '" /></a>';
  465. };
  466. // Simplest case possible: a linked image linking to the same image.
  467. $img_base_url = base_path() . 'foo/bar/image';
  468. $a_url = $img_url = $img_base_url . '.png';
  469. $html = $template($a_url, $img_url);
  470. cdn_html_alter_image_urls($html);
  471. $this->assertIdentical($template($cdn . $a_url, $cdn . $img_url), $html, 'Linked image HTML correctly altered.');
  472. // Slightly more complex: a linked image linking to a derivative image.
  473. $img_url = $img_base_url . '-thumbnail.png?itok=5678wxyz';
  474. $html = $template($a_url, $img_url);
  475. cdn_html_alter_image_urls($html);
  476. $this->assertIdentical($template($cdn . $a_url, $cdn . $img_url), $html, 'Linked image HTML correctly altered.');
  477. // Slightly more complex: a linked derivative image linking to another
  478. // derivative image.
  479. $a_url = $img_base_url . '-large.png?itok=9012klmn';
  480. $img_url = $img_base_url . '-thumbnail.png?itok=5678wxyz';
  481. $html = $template($a_url, $img_url);
  482. cdn_html_alter_image_urls($html);
  483. $this->assertIdentical($template($cdn . $a_url, $cdn . $img_url), $html, 'Linked image HTML correctly altered.');
  484. // Ensure that images linking to content (i.e. not a bigger version of the
  485. // image) don't get their anchors modified
  486. $a_url = base_path() . 'node';
  487. $html = $template($a_url, $img_url);
  488. cdn_html_alter_image_urls($html);
  489. $this->assertIdentical($template($a_url, $cdn . $img_url), $html, 'Linked image HTML correctly altered (anchor unmodified).');
  490. // Same, but now for a link with a query string.
  491. $a_url = base_path() . 'node?foobar';
  492. $html = $template($a_url, $img_url);
  493. cdn_html_alter_image_urls($html);
  494. $this->assertIdentical($template($a_url, $cdn . $img_url), $html, 'Linked image HTML correctly altered (anchor unmodified, even with query strings).');
  495. }
  496. }