common.test 112 KB
Newer Older
1
2
<?php

3
4
5
6
7
8
9
10
/**
 * @file
 * Tests for common.inc functionality.
 */

/**
 * Tests for URL generation functions.
 */
11
class CommonDrupalAlterTestCase extends DrupalWebTestCase {
12
13
  public static function getInfo() {
    return array(
14
15
16
      'name' => 'Alter hook functionality',
      'description' => 'Tests alteration of arguments passed to drupal_alter().',
      'group' => 'Common',
17
18
19
20
21
22
23
24
    );
  }

  function setUp() {
    parent::setUp('common_test');
  }

  function testDrupalAlter() {
25
    // This test depends on Bartik, so make sure that it is always the current
26
27
    // active theme.
    global $theme, $base_theme_info;
28
    $theme = 'bartik';
29
30
    $base_theme_info = array();

31
    $array = array('foo' => 'bar');
32
    $entity = new stdClass();
33
    $entity->foo = 'bar';
34
35
36

    // Verify alteration of a single argument.
    $array_copy = $array;
37
    $array_expected = array('foo' => 'Drupal theme');
38
    drupal_alter('drupal_alter', $array_copy);
39
    $this->assertEqual($array_copy, $array_expected, t('Single array was altered.'));
40

41
42
43
44
    $entity_copy = clone $entity;
    $entity_expected = clone $entity;
    $entity_expected->foo = 'Drupal theme';
    drupal_alter('drupal_alter', $entity_copy);
45
    $this->assertEqual($entity_copy, $entity_expected, t('Single object was altered.'));
46
47
48

    // Verify alteration of multiple arguments.
    $array_copy = $array;
49
    $array_expected = array('foo' => 'Drupal theme');
50
51
52
    $entity_copy = clone $entity;
    $entity_expected = clone $entity;
    $entity_expected->foo = 'Drupal theme';
53
    $array2_copy = $array;
54
    $array2_expected = array('foo' => 'Drupal theme');
55
    drupal_alter('drupal_alter', $array_copy, $entity_copy, $array2_copy);
56
57
58
    $this->assertEqual($array_copy, $array_expected, t('First argument to drupal_alter() was altered.'));
    $this->assertEqual($entity_copy, $entity_expected, t('Second argument to drupal_alter() was altered.'));
    $this->assertEqual($array2_copy, $array2_expected, t('Third argument to drupal_alter() was altered.'));
59
60
61
62
63
64
65
66

    // Verify alteration order when passing an array of types to drupal_alter().
    // common_test_module_implements_alter() places 'block' implementation after
    // other modules.
    $array_copy = $array;
    $array_expected = array('foo' => 'Drupal block theme');
    drupal_alter(array('drupal_alter', 'drupal_alter_foo'), $array_copy);
    $this->assertEqual($array_copy, $array_expected, t('hook_TYPE_alter() implementations ran in correct order.'));
67
68
69
  }
}

70
/**
71
 * Tests for URL generation functions.
72
73
74
 *
 * url() calls module_implements(), which may issue a db query, which requires
 * inheriting from a web test case rather than a unit test case.
75
 */
76
class CommonURLUnitTestCase extends DrupalWebTestCase {
77
  public static function getInfo() {
78
    return array(
79
      'name' => 'URL generation tests',
80
      'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.',
81
      'group' => 'Common',
82
83
84
85
86
87
88
89
90
91
92
    );
  }

  /**
   * Confirm that invalid text given as $path is filtered.
   */
  function testLXSS() {
    $text = $this->randomName();
    $path = "<SCRIPT>alert('XSS')</SCRIPT>";
    $link = l($text, $path);
    $sanitized_path = check_url(url($path));
93
    $this->assertTrue(strpos($link, $sanitized_path) !== FALSE, t('XSS attack @path was filtered', array('@path' => $path)));
94
  }
95

96
97
98
99
100
  /*
   * Tests for active class in l() function.
   */
  function testLActiveClass() {
    $link = l($this->randomName(), $_GET['q']);
101
    $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active')));
102
103
104
105
106
107
108
109
  }

  /**
   * Tests for custom class in l() function.
   */
  function testLCustomClass() {
    $class = $this->randomName();
    $link = l($this->randomName(), $_GET['q'], array('attributes' => array('class' => array($class))));
110
111
    $this->assertTrue($this->hasClass($link, $class), t('Custom class @class is present on link when requested', array('@class' => $class)));
    $this->assertTrue($this->hasClass($link, 'active'), t('Class @class is present on link to the current page', array('@class' => 'active')));
112
113
114
115
116
  }

  private function hasClass($link, $class) {
    return preg_match('|class="([^\"\s]+\s+)*' . $class . '|', $link);
  }
117

118
  /**
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
   * Test drupal_get_query_parameters().
   */
  function testDrupalGetQueryParameters() {
    $original = array(
      'a' => 1,
      'b' => array(
        'd' => 4,
        'e' => array(
          'f' => 5,
        ),
      ),
      'c' => 3,
      'q' => 'foo/bar',
    );

    // Default arguments.
    $result = $_GET;
    unset($result['q']);
137
    $this->assertEqual(drupal_get_query_parameters(), $result, t("\$_GET['q'] was removed."));
138
139
140
141

    // Default exclusion.
    $result = $original;
    unset($result['q']);
142
    $this->assertEqual(drupal_get_query_parameters($original), $result, t("'q' was removed."));
143
144
145
146

    // First-level exclusion.
    $result = $original;
    unset($result['b']);
147
    $this->assertEqual(drupal_get_query_parameters($original, array('b')), $result, t("'b' was removed."));
148
149
150
151

    // Second-level exclusion.
    $result = $original;
    unset($result['b']['d']);
152
    $this->assertEqual(drupal_get_query_parameters($original, array('b[d]')), $result, t("'b[d]' was removed."));
153
154
155
156

    // Third-level exclusion.
    $result = $original;
    unset($result['b']['e']['f']);
157
    $this->assertEqual(drupal_get_query_parameters($original, array('b[e][f]')), $result, t("'b[e][f]' was removed."));
158
159
160
161

    // Multiple exclusions.
    $result = $original;
    unset($result['a'], $result['b']['e'], $result['c']);
162
    $this->assertEqual(drupal_get_query_parameters($original, array('a', 'b[e]', 'c')), $result, t("'a', 'b[e]', 'c' were removed."));
163
164
165
166
167
168
  }

  /**
   * Test drupal_http_build_query().
   */
  function testDrupalHttpBuildQuery() {
169
170
171
172
    $this->assertEqual(drupal_http_build_query(array('a' => ' &#//+%20@۞')), 'a=%20%26%23//%2B%2520%40%DB%9E', t('Value was properly encoded.'));
    $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', t('Key was properly encoded.'));
    $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', t('Multiple values were properly concatenated.'));
    $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', t('Nested array was properly encoded.'));
173
174
175
176
177
178
179
180
181
182
183
184
185
  }

  /**
   * Test drupal_parse_url().
   */
  function testDrupalParseUrl() {
    // Relative URL.
    $url = 'foo/bar?foo=bar&bar=baz&baz#foo';
    $result = array(
      'path' => 'foo/bar',
      'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''),
      'fragment' => 'foo',
    );
186
    $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.'));
187

188
189
190
191
192
193
194
    // Relative URL that is known to confuse parse_url().
    $url = 'foo/bar:1';
    $result = array(
      'path' => 'foo/bar:1',
      'query' => array(),
      'fragment' => '',
    );
195
    $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL parsed correctly.'));
196

197
198
199
200
201
202
203
    // Absolute URL.
    $url = '/foo/bar?foo=bar&bar=baz&baz#foo';
    $result = array(
      'path' => '/foo/bar',
      'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''),
      'fragment' => 'foo',
    );
204
    $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL parsed correctly.'));
205

206
    // External URL testing.
207
    $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
208
209

    // Test that drupal can recognize an absolute URL. Used to prevent attack vectors.
210
    $this->assertTrue(url_is_external($url), t('Correctly identified an external URL.'));
211
212

    // Test the parsing of absolute URLs.
213
214
215
216
217
    $result = array(
      'path' => 'http://drupal.org/foo/bar',
      'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''),
      'fragment' => 'foo',
    );
218
    $this->assertEqual(drupal_parse_url($url), $result, t('External URL parsed correctly.'));
219
220
221
222
223
224
225
226
227

    // Verify proper parsing of URLs when clean URLs are disabled.
    $result = array(
      'path' => 'foo/bar',
      'query' => array('bar' => 'baz'),
      'fragment' => 'foo',
    );
    // Non-clean URLs #1: Absolute URL generated by url().
    $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo';
228
    $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL with clean URLs disabled parsed correctly.'));
229
230
231

    // Non-clean URLs #2: Relative URL generated by url().
    $url = '?q=foo/bar&bar=baz#foo';
232
    $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL with clean URLs disabled parsed correctly.'));
233
234
235

    // Non-clean URLs #3: URL generated by url() on non-Apache webserver.
    $url = 'index.php?q=foo/bar&bar=baz#foo';
236
    $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.'));
237
238
239

    // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect.
    $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html');
240
    $this->assertFalse(valid_url($parts['path'], TRUE), t('drupal_parse_url() correctly parsed a forged URL.'));
241
242
  }

243
  /**
244
245
   * Test url() with/without query, with/without fragment, absolute on/off and
   * assert all that works when clean URLs are on and off.
246
247
248
   */
  function testUrl() {
    global $base_url;
249

250
251
252
253
    foreach (array(FALSE, TRUE) as $absolute) {
      // Get the expected start of the path string.
      $base = $absolute ? $base_url . '/' : base_path();
      $absolute_string = $absolute ? 'absolute' : NULL;
254
255

      // Disable Clean URLs.
256
      $GLOBALS['conf']['clean_url'] = 0;
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277

      $url = $base . '?q=node/123';
      $result = url('node/123', array('absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . '?q=node/123#foo';
      $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . '?q=node/123&foo';
      $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . '?q=node/123&foo=bar&bar=baz';
      $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . '?q=node/123&foo#bar';
      $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

278
279
280
281
282
283
284
285
      $url = $base . '?q=node/123&foo#0';
      $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '0', 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . '?q=node/123&foo';
      $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => '', 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

286
287
288
289
290
      $url = $base;
      $result = url('<front>', array('absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      // Enable Clean URLs.
291
      $GLOBALS['conf']['clean_url'] = 1;
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315

      $url = $base . 'node/123';
      $result = url('node/123', array('absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . 'node/123#foo';
      $result = url('node/123', array('fragment' => 'foo', 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . 'node/123?foo';
      $result = url('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . 'node/123?foo=bar&bar=baz';
      $result = url('node/123', array('query' => array('foo' => 'bar', 'bar' => 'baz'), 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base . 'node/123?foo#bar';
      $result = url('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");

      $url = $base;
      $result = url('<front>', array('absolute' => $absolute));
      $this->assertEqual($url, $result, "$url == $result");
316
317
    }
  }
318
319
320
321
322
323
324
325
326
327

  /**
   * Test external URL handling.
   */
  function testExternalUrls() {
    $test_url = 'http://drupal.org/';

    // Verify external URL can contain a fragment.
    $url = $test_url . '#drupal';
    $result = url($url);
328
    $this->assertEqual($url, $result, t('External URL with fragment works without a fragment in $options.'));
329
330
331
332
333

    // Verify fragment can be overidden in an external URL.
    $url = $test_url . '#drupal';
    $fragment = $this->randomName(10);
    $result = url($url, array('fragment' => $fragment));
334
    $this->assertEqual($test_url . '#' . $fragment, $result, t('External URL fragment is overidden with a custom fragment in $options.'));
335
336
337
338

    // Verify external URL can contain a query string.
    $url = $test_url . '?drupal=awesome';
    $result = url($url);
339
    $this->assertEqual($url, $result, t('External URL with query string works without a query string in $options.'));
340
341
342
343
344

    // Verify external URL can be extended with a query string.
    $url = $test_url;
    $query = array($this->randomName(5) => $this->randomName(5));
    $result = url($url, array('query' => $query));
345
    $this->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, t('External URL can be extended with a query string in $options.'));
346
347
348
349
350

    // Verify query string can be extended in an external URL.
    $url = $test_url . '?drupal=awesome';
    $query = array($this->randomName(5) => $this->randomName(5));
    $result = url($url, array('query' => $query));
351
    $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, t('External URL query string can be extended with a custom query string in $options.'));
352
  }
353
354
}

355
/**
356
 * Tests for the check_plain(), filter_xss() and format_string() functions.
357
 */
358
class CommonXssUnitTestCase extends DrupalUnitTestCase {
359
360
361
362

  public static function getInfo() {
    return array(
      'name' => 'String filtering tests',
363
      'description' => 'Confirm that check_plain(), filter_xss(), format_string() and check_url() work correctly, including invalid multi-byte sequences.',
364
      'group' => 'Common',
365
366
367
368
369
370
371
    );
  }

  /**
   * Check that invalid multi-byte sequences are rejected.
   */
  function testInvalidMultiByte() {
372
373
374
     // Ignore PHP 5.3+ invalid multibyte sequence warning.
     $text = @check_plain("Foo\xC0barbaz");
     $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"');
375
376
377
     // Ignore PHP 5.3+ invalid multibyte sequence warning.
     $text = @check_plain("\xc2\"");
     $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "\xc2\""');
378
379
380
381
382
383
     $text = check_plain("Fooÿñ");
     $this->assertEqual($text, "Fooÿñ", 'check_plain() accepts valid sequence "Fooÿñ"');
     $text = filter_xss("Foo\xC0barbaz");
     $this->assertEqual($text, '', 'filter_xss() rejects invalid sequence "Foo\xC0barbaz"');
     $text = filter_xss("Fooÿñ");
     $this->assertEqual($text, "Fooÿñ", 'filter_xss() accepts valid sequence Fooÿñ');
384
385
386
387
388
389
  }

  /**
   * Check that special characters are escaped.
   */
  function testEscaping() {
390
391
     $text = check_plain("<script>");
     $this->assertEqual($text, '&lt;script&gt;', 'check_plain() escapes &lt;script&gt;');
392
393
     $text = check_plain('<>&"\'');
     $this->assertEqual($text, '&lt;&gt;&amp;&quot;&#039;', 'check_plain() escapes reserved HTML characters.');
394
  }
395

396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
  /**
   * Test t() and format_string() replacement functionality.
   */
  function testFormatStringAndT() {
    foreach (array('format_string', 't') as $function) {
      $text = $function('Simple text');
      $this->assertEqual($text, 'Simple text', $function . ' leaves simple text alone.');
      $text = $function('Escaped text: @value', array('@value' => '<script>'));
      $this->assertEqual($text, 'Escaped text: &lt;script&gt;', $function . ' replaces and escapes string.');
      $text = $function('Placeholder text: %value', array('%value' => '<script>'));
      $this->assertEqual($text, 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', $function . ' replaces, escapes and themes string.');
      $text = $function('Verbatim text: !value', array('!value' => '<script>'));
      $this->assertEqual($text, 'Verbatim text: <script>', $function . ' replaces verbatim string as-is.');
    }
  }

412
413
414
415
416
417
418
419
420
421
  /**
   * Check that harmful protocols are stripped.
   */
  function testBadProtocolStripping() {
    // Ensure that check_url() strips out harmful protocols, and encodes for
    // HTML. Ensure drupal_strip_dangerous_protocols() can be used to return a
    // plain-text string stripped of harmful protocols.
    $url = 'javascript:http://www.example.com/?x=1&y=2';
    $expected_plain = 'http://www.example.com/?x=1&y=2';
    $expected_html = 'http://www.example.com/?x=1&amp;y=2';
422
423
    $this->assertIdentical(check_url($url), $expected_html, t('check_url() filters a URL and encodes it for HTML.'));
    $this->assertIdentical(drupal_strip_dangerous_protocols($url), $expected_plain, t('drupal_strip_dangerous_protocols() filters a URL and returns plain text.'));
424
  }
425
426
}

427
class CommonSizeUnitTestCase extends DrupalUnitTestCase {
428
429
  protected $exact_test_cases;
  protected $rounded_test_cases;
430

431
  public static function getInfo() {
432
    return array(
433
434
      'name' => 'Size parsing test',
      'description' => 'Parse a predefined amount of bytes and compare the output with the expected value.',
435
      'group' => 'Common',
436
437
438
439
    );
  }

  function setUp() {
440
    $kb = DRUPAL_KILOBYTE;
441
    $this->exact_test_cases = array(
442
443
444
445
446
447
448
449
450
      '1 byte' => 1,
      '1 KB'   => $kb,
      '1 MB'   => $kb * $kb,
      '1 GB'   => $kb * $kb * $kb,
      '1 TB'   => $kb * $kb * $kb * $kb,
      '1 PB'   => $kb * $kb * $kb * $kb * $kb,
      '1 EB'   => $kb * $kb * $kb * $kb * $kb * $kb,
      '1 ZB'   => $kb * $kb * $kb * $kb * $kb * $kb * $kb,
      '1 YB'   => $kb * $kb * $kb * $kb * $kb * $kb * $kb * $kb,
451
452
    );
    $this->rounded_test_cases = array(
453
454
455
456
457
      '2 bytes' => 2,
      '1 MB' => ($kb * $kb) - 1, // rounded to 1 MB (not 1000 or 1024 kilobyte!)
      round(3623651 / ($this->exact_test_cases['1 MB']), 2) . ' MB' => 3623651, // megabytes
      round(67234178751368124 / ($this->exact_test_cases['1 PB']), 2) . ' PB' => 67234178751368124, // petabytes
      round(235346823821125814962843827 / ($this->exact_test_cases['1 YB']), 2) . ' YB' => 235346823821125814962843827, // yottabytes
458
459
460
461
462
    );
    parent::setUp();
  }

  /**
463
   * Check that format_size() returns the expected string.
464
465
466
   */
  function testCommonFormatSize() {
    foreach (array($this->exact_test_cases, $this->rounded_test_cases) as $test_cases) {
467
468
469
470
471
      foreach ($test_cases as $expected => $input) {
        $this->assertEqual(
          ($result = format_size($input, NULL)),
          $expected,
          $expected . ' == ' . $result . ' (' . $input . ' bytes)'
472
473
474
475
        );
      }
    }
  }
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521

  /**
   * Check that parse_size() returns the proper byte sizes.
   */
  function testCommonParseSize() {
    foreach ($this->exact_test_cases as $string => $size) {
      $this->assertEqual(
        $parsed_size = parse_size($string),
        $size,
        $size . ' == ' . $parsed_size . ' (' . $string . ')'
      );
    }

    // Some custom parsing tests
    $string = '23476892 bytes';
    $this->assertEqual(
      ($parsed_size = parse_size($string)),
      $size = 23476892,
      $string . ' == ' . $parsed_size . ' bytes'
    );
    $string = '76MRandomStringThatShouldBeIgnoredByParseSize.'; // 76 MB
    $this->assertEqual(
      $parsed_size = parse_size($string),
      $size = 79691776,
      $string . ' == ' . $parsed_size . ' bytes'
    );
    $string = '76.24 Giggabyte'; // Misspeld text -> 76.24 GB
    $this->assertEqual(
      $parsed_size = parse_size($string),
      $size = 81862076662,
      $string . ' == ' . $parsed_size . ' bytes'
    );
  }

  /**
   * Cross-test parse_size() and format_size().
   */
  function testCommonParseSizeFormatSize() {
    foreach ($this->exact_test_cases as $size) {
      $this->assertEqual(
        $size,
        ($parsed_size = parse_size($string = format_size($size, NULL))),
        $size . ' == ' . $parsed_size . ' (' . $string . ')'
      );
    }
  }
522
}
523
524
525
526

/**
 * Test drupal_explode_tags() and drupal_implode_tags().
 */
527
class CommonAutocompleteTagsTestCase extends DrupalWebTestCase {
528
529
530
531
532
533
534
  var $validTags = array(
    'Drupal' => 'Drupal',
    'Drupal with some spaces' => 'Drupal with some spaces',
    '"Legendary Drupal mascot of doom: ""Druplicon"""' => 'Legendary Drupal mascot of doom: "Druplicon"',
    '"Drupal, although it rhymes with sloopal, is as awesome as a troopal!"' => 'Drupal, although it rhymes with sloopal, is as awesome as a troopal!',
  );

535
  public static function getInfo() {
536
    return array(
537
538
539
      'name' => 'Autocomplete tags',
      'description' => 'Tests explosion and implosion of autocomplete tags.',
      'group' => 'Common',
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
    );
  }

  /**
   * Explode a series of tags.
   */
  function testDrupalExplodeTags() {
    $string = implode(', ', array_keys($this->validTags));
    $tags = drupal_explode_tags($string);
    $this->assertTags($tags);
  }

  /**
   * Implode a series of tags.
   */
  function testDrupalImplodeTags() {
    $tags = array_values($this->validTags);
    // Let's explode and implode to our heart's content.
    for ($i = 0; $i < 10; $i++) {
      $string = drupal_implode_tags($tags);
      $tags = drupal_explode_tags($string);
    }
    $this->assertTags($tags);
  }

  /**
   * Helper function: asserts that the ending array of tags is what we wanted.
   */
  function assertTags($tags) {
    $original = $this->validTags;
    foreach ($tags as $tag) {
      $key = array_search($tag, $original);
572
      $this->assertTrue($key, t('Make sure tag %tag shows up in the final tags array (originally %original)', array('%tag' => $tag, '%original' => $key)));
573
574
575
      unset($original[$key]);
    }
    foreach ($original as $leftover) {
576
      $this->fail(t('Leftover tag %leftover was left over.', array('%leftover' => $leftover)));
577
578
    }
  }
579
580
}

581
582
583
/**
 * Test the Drupal CSS system.
 */
584
class CommonCascadingStylesheetsTestCase extends DrupalWebTestCase {
585
  public static function getInfo() {
586
    return array(
587
588
      'name' => 'Cascading stylesheets',
      'description' => 'Tests adding various cascading stylesheets to the page.',
589
      'group' => 'Common',
590
591
592
593
    );
  }

  function setUp() {
594
    parent::setUp('php', 'locale', 'common_test');
595
    // Reset drupal_add_css() before each test.
596
    drupal_static_reset('drupal_add_css');
597
598
599
600
601
602
  }

  /**
   * Check default stylesheets as empty.
   */
  function testDefault() {
603
    $this->assertEqual(array(), drupal_add_css(), t('Default CSS is empty.'));
604
605
  }

606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
  /**
   * Test that stylesheets in module .info files are loaded.
   */
  function testModuleInfo() {
    $this->drupalGet('');

    // Verify common_test.css in a STYLE media="all" tag.
    $elements = $this->xpath('//style[@media=:media and contains(text(), :filename)]', array(
      ':media' => 'all',
      ':filename' => 'tests/common_test.css',
    ));
    $this->assertTrue(count($elements), "Stylesheet with media 'all' in module .info file found.");

    // Verify common_test.print.css in a STYLE media="print" tag.
    $elements = $this->xpath('//style[@media=:media and contains(text(), :filename)]', array(
      ':media' => 'print',
      ':filename' => 'tests/common_test.print.css',
    ));
    $this->assertTrue(count($elements), "Stylesheet with media 'print' in module .info file found.");
  }

627
628
629
630
631
632
  /**
   * Tests adding a file stylesheet.
   */
  function testAddFile() {
    $path = drupal_get_path('module', 'simpletest') . '/simpletest.css';
    $css = drupal_add_css($path);
633
    $this->assertEqual($css[$path]['data'], $path, t('Adding a CSS file caches it properly.'));
634
635
  }

636
637
638
639
640
641
  /**
   * Tests adding an external stylesheet.
   */
  function testAddExternal() {
    $path = 'http://example.com/style.css';
    $css = drupal_add_css($path, 'external');
642
    $this->assertEqual($css[$path]['type'], 'external', t('Adding an external CSS file caches it properly.'));
643
644
  }

645
646
647
648
  /**
   * Makes sure that reseting the CSS empties the cache.
   */
  function testReset() {
649
    drupal_static_reset('drupal_add_css');
650
    $this->assertEqual(array(), drupal_add_css(), t('Resetting the CSS empties the cache.'));
651
652
  }

653
654
655
656
657
658
  /**
   * Tests rendering the stylesheets.
   */
  function testRenderFile() {
    $css = drupal_get_path('module', 'simpletest') . '/simpletest.css';
    drupal_add_css($css);
659
    $styles = drupal_get_css();
660
    $this->assertTrue(strpos($styles, $css) > 0, t('Rendered CSS includes the added stylesheet.'));
661
662
663
664
665
666
667
668
669
  }

  /**
   * Tests rendering an external stylesheet.
   */
  function testRenderExternal() {
    $css = 'http://example.com/style.css';
    drupal_add_css($css, 'external');
    $styles = drupal_get_css();
670
671
    // Stylesheet URL may be the href of a LINK tag or in an @import statement
    // of a STYLE tag.
672
    $this->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, t('Rendering an external CSS file.'));
673
  }
674
675
676
677
678
679

  /**
   * Tests rendering inline stylesheets with preprocessing on.
   */
  function testRenderInlinePreprocess() {
    $css = 'body { padding: 0px; }';
680
    $css_preprocessed = '<style media="all">' . "\n<!--/*--><![CDATA[/*><!--*/\n" . drupal_load_stylesheet_content($css, TRUE) . "\n/*]]>*/-->\n" . '</style>';
681
    drupal_add_css($css, array('type' => 'inline'));
682
    $styles = drupal_get_css();
683
    $this->assertEqual(trim($styles), $css_preprocessed, t('Rendering preprocessed inline CSS adds it to the page.'));
684
685
686
687
688
689
690
  }

  /**
   * Tests rendering inline stylesheets with preprocessing off.
   */
  function testRenderInlineNoPreprocess() {
    $css = 'body { padding: 0px; }';
691
    drupal_add_css($css, array('type' => 'inline', 'preprocess' => FALSE));
692
    $styles = drupal_get_css();
693
    $this->assertTrue(strpos($styles, $css) > 0, t('Rendering non-preprocessed inline CSS adds it to the page.'));
694
695
696
697
698
699
  }

  /**
   * Tests rendering inline stylesheets through a full page request.
   */
  function testRenderInlineFullPage() {
700
    $css = 'body { font-size: 254px; }';
701
702
703
    // Inline CSS is minified unless 'preprocess' => FALSE is passed as a
    // drupal_add_css() option.
    $expected = 'body{font-size:254px;}';
704
705

    // Create a node, using the PHP filter that tests drupal_add_css().
706
    $php_format_id = 'php_code';
707
708
    $settings = array(
      'type' => 'page',
709
      'body' => array(
710
        LANGUAGE_NONE => array(
711
712
          array(
            'value' => t('This tests the inline CSS!') . "<?php drupal_add_css('$css', 'inline'); ?>",
713
            'format' => $php_format_id,
714
715
716
          ),
        ),
      ),
717
718
719
720
721
722
      'promote' => 1,
    );
    $node = $this->drupalCreateNode($settings);

    // Fetch the page.
    $this->drupalGet('node/' . $node->nid);
723
    $this->assertRaw($expected, t('Inline stylesheets appear in the full page rendering.'));
724
  }
725
726
727
728
729
730
731
732
733

  /**
   * Test CSS ordering.
   */
  function testRenderOrder() {
    // A module CSS file.
    drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
    // A few system CSS files, ordered in a strange way.
    $system_path = drupal_get_path('module', 'system');
734
735
    drupal_add_css($system_path . '/system.base.css', array('group' => CSS_SYSTEM, 'weight' => -10));
    drupal_add_css($system_path . '/system.theme.css', array('group' => CSS_SYSTEM));
736
737

    $expected = array(
738
739
      $system_path . '/system.base.css',
      $system_path . '/system.theme.css',
740
741
742
      drupal_get_path('module', 'simpletest') . '/simpletest.css',
    );

743

744
    $styles = drupal_get_css();
745
746
747
748
    // Stylesheet URL may be the href of a LINK tag or in an @import statement
    // of a STYLE tag.
    if (preg_match_all('/(href="|url\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\?/', $styles, $matches)) {
      $result = $matches[2];
749
750
751
752
753
    }
    else {
      $result = array();
    }

754
    $this->assertIdentical($result, $expected, t('The CSS files are in the expected order.'));
755
756
757
758
759
760
  }

  /**
   * Test CSS override.
   */
  function testRenderOverride() {
761
762
763
764
765
    $system = drupal_get_path('module', 'system');
    $simpletest = drupal_get_path('module', 'simpletest');

    drupal_add_css($system . '/system.base.css');
    drupal_add_css($simpletest . '/tests/system.base.css');
766
767

    // The dummy stylesheet should be the only one included.
768
    $styles = drupal_get_css();
769
770
    $this->assert(strpos($styles, $simpletest . '/tests/system.base.css') !== FALSE, t('The overriding CSS file is output.'));
    $this->assert(strpos($styles, $system . '/system.base.css') === FALSE, t('The overridden CSS file is not output.'));
771

772
773
    drupal_add_css($simpletest . '/tests/system.base.css');
    drupal_add_css($system . '/system.base.css');
774
775

    // The standard stylesheet should be the only one included.
776
    $styles = drupal_get_css();
777
778
    $this->assert(strpos($styles, $system . '/system.base.css') !== FALSE, t('The overriding CSS file is output.'));
    $this->assert(strpos($styles, $simpletest . '/tests/system.base.css') === FALSE, t('The overridden CSS file is not output.'));
779
780
781
782
783
784
  }

  /**
   * Tests Locale module's CSS Alter to include RTL overrides.
   */
  function testAlter() {
785
    // Switch the language to a right to left language and add system.base.css.
786
787
    global $language;
    $language->direction = LANGUAGE_RTL;
788
789
    $path = drupal_get_path('module', 'system');
    drupal_add_css($path . '/system.base.css');
790

791
    // Check to see if system.base-rtl.css was also added.
792
    $styles = drupal_get_css();
793
    $this->assert(strpos($styles, $path . '/system.base-rtl.css') !== FALSE, t('CSS is alterable as right to left overrides are added.'));
794
795
796
797

    // Change the language back to left to right.
    $language->direction = LANGUAGE_LTR;
  }
798
799
800
801
802
803
804

  /**
   * Tests that the query string remains intact when adding CSS files that have
   * query string parameters.
   */
  function testAddCssFileWithQueryString() {
    $this->drupalGet('common-test/query-string');
805
806
807
    $query_string = variable_get('css_js_query_string', '0');
    $this->assertRaw(drupal_get_path('module', 'node') . '/node.css?' . $query_string, t('Query string was appended correctly to css.'));
    $this->assertRaw(drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&amp;arg2=value2', t('Query string not escaped on a URI.'));
808
  }
809
810
}

811
/**
812
 * Test for cleaning HTML identifiers.
813
 */
814
class CommonHTMLIdentifierTestCase extends DrupalUnitTestCase {
815
816
  public static function getInfo() {
    return array(
817
818
      'name' => 'HTML identifiers',
      'description' => 'Test the functions drupal_html_class(), drupal_html_id() and drupal_clean_css_identifier() for expected behavior',
819
      'group' => 'Common',
820
821
822
823
    );
  }

  /**
824
   * Tests that drupal_clean_css_identifier() cleans the identifier properly.
825
826
   */
  function testDrupalCleanCSSIdentifier() {
827
828
    // Verify that no valid ASCII characters are stripped from the identifier.
    $identifier = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
829
    $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, t('Verify valid ASCII characters pass through.'));
830

831
832
    // Verify that valid UTF-8 characters are not stripped from the identifier.
    $identifier = '¡¢£¤¥';
833
    $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, t('Verify valid UTF-8 characters pass through.'));
834

835
    // Verify that invalid characters (including non-breaking space) are stripped from the identifier.
836
    $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', t('Strip invalid characters.'));
837
838
839
  }

  /**
840
   * Tests that drupal_html_class() cleans the class name properly.
841
   */
842
  function testDrupalHTMLClass() {
843
    // Verify Drupal coding standards are enforced.
844
    $this->assertIdentical(drupal_html_class('CLASS NAME_[Ü]'), 'class-name--ü', t('Enforce Drupal coding standards.'));
845
846
847
  }

  /**
848
   * Tests that drupal_html_id() cleans the ID properly.
849
   */
850
851
852
  function testDrupalHTMLId() {
    // Verify that letters, digits, and hyphens are not stripped from the ID.
    $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
853
    $this->assertIdentical(drupal_html_id($id), $id, t('Verify valid characters pass through.'));
854
855

    // Verify that invalid characters are stripped from the ID.
856
    $this->assertIdentical(drupal_html_id('invalid,./:@\\^`{Üidentifier'), 'invalididentifier', t('Strip invalid characters.'));
857

858
    // Verify Drupal coding standards are enforced.
859
    $this->assertIdentical(drupal_html_id('ID NAME_[1]'), 'id-name-1', t('Enforce Drupal coding standards.'));
860
861

    // Reset the static cache so we can ensure the unique id count is at zero.
862
    drupal_static_reset('drupal_html_id');
863
864

    // Clean up IDs with invalid starting characters.
865
866
867
    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id', t('Test the uniqueness of IDs #1.'));
    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--2', t('Test the uniqueness of IDs #2.'));
    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--3', t('Test the uniqueness of IDs #3.'));
868
869
870
  }
}

871
872
873
/**
 * CSS Unit Tests.
 */
874
class CommonCascadingStylesheetsUnitTestCase extends DrupalUnitTestCase {
875
876
877
878
  public static function getInfo() {
    return array(
      'name' => 'CSS Unit Tests',
      'description' => 'Unit tests on CSS functions like aggregation.',
879
      'group' => 'Common',
880
881
882
883
    );
  }

  /**
884
885
   * Tests basic CSS loading with and without optimization via drupal_load_stylesheet().
   *
886
887
888
889
   * Known tests:
   * - Retain white-space in selectors. (http://drupal.org/node/472820)
   * - Proper URLs in imported files. (http://drupal.org/node/265719)
   * - Retain pseudo-selectors. (http://drupal.org/node/460448)
890
891
   */
  function testLoadCssBasic() {
892
893
894
895
896
897
    // Array of files to test living in 'simpletest/files/css_test_files/'.
    // - Original: name.css
    // - Unoptimized expected content: name.css.unoptimized.css
    // - Optimized expected content: name.css.optimized.css
    $testfiles = array(
      'css_input_without_import.css',
898
899
      'css_input_with_import.css',
      'comment_hacks.css'
900
    );
901
902
903
    $path = drupal_get_path('module', 'simpletest') . '/files/css_test_files';
    foreach ($testfiles as $file) {
      $expected = file_get_contents("$path/$file.unoptimized.css");
904
      $unoptimized_output = drupal_load_stylesheet("$path/$file.unoptimized.css", FALSE);
905
      $this->assertEqual($unoptimized_output, $expected, t('Unoptimized CSS file has expected contents (@file)', array('@file' => $file)));
906

907
908
      $expected = file_get_contents("$path/$file.optimized.css");
      $optimized_output = drupal_load_stylesheet("$path/$file", TRUE);
909
      $this->assertEqual($optimized_output, $expected, t('Optimized CSS file has expected contents (@file)', array('@file' => $file)));
910
911
912
913
914
915
916
917
918

      // Repeat the tests by accessing the stylesheets by URL.
      $expected = file_get_contents("$path/$file.unoptimized.css");
      $unoptimized_output_url = drupal_load_stylesheet($GLOBALS['base_url'] . "/$path/$file.unoptimized.css", FALSE);
      $this->assertEqual($unoptimized_output, $expected, t('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file)));

      $expected = file_get_contents("$path/$file.optimized.css");
      $optimized_output = drupal_load_stylesheet($GLOBALS['base_url'] . "/$path/$file", TRUE);
      $this->assertEqual($optimized_output, $expected, t('Optimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file)));
919
920
921
922
    }
  }
}

923
924
925
/**
 * Test drupal_http_request().
 */
926
class CommonDrupalHTTPRequestTestCase extends DrupalWebTestCase {
927
  public static function getInfo() {
928
    return array(
929
930
      'name' => 'Drupal HTTP request',
      'description' => "Performs tests on Drupal's HTTP request mechanism.",
931
      'group' => 'Common',
932
933
934
    );
  }

935
936
937
938
  function setUp() {
    parent::setUp('system_test');
  }

939
  function testDrupalHTTPRequest() {
940
941
    global $is_https;

942
    // Parse URL schema.
943
    $missing_scheme = drupal_http_request('example.com/path');
944
945
    $this->assertEqual($missing_scheme->code, -1002, t('Returned with "-1002" error code.'));
    $this->assertEqual($missing_scheme->error, 'missing schema', t('Returned with "missing schema" error message.'));
946
947

    $unable_to_parse = drupal_http_request('http:///path');
948
949
    $this->assertEqual($unable_to_parse->code, -1001, t('Returned with "-1001" error code.'));
    $this->assertEqual($unable_to_parse->error, 'unable to parse URL', t('Returned with "unable to parse URL" error message.'));
950
951
952

    // Fetch page.
    $result = drupal_http_request(url('node', array('absolute' => TRUE)));
953
    $this->assertEqual($result->code, 200, t('Fetched page successfully.'));
954
    $this->drupalSetContent($result->data);
955
    $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), t('Site title matches.'));
956
957
958

    // Test that code and status message is returned.
    $result = drupal_http_request(url('pagedoesnotexist', array('absolute' => TRUE)));
959
960
961
    $this->assertTrue(!empty($result->protocol),  t('Result protocol is returned.'));
    $this->assertEqual($result->code, '404', t('Result code is 404'));
    $this->assertEqual($result->status_message, 'Not Found', t('Result status message is "Not Found"'));
962

963
964
965
966
967
968
969
970
971
972
973
974
975
    // Skip the timeout tests when the testing environment is HTTPS because
    // stream_set_timeout() does not work for SSL connections.
    // @link http://bugs.php.net/bug.php?id=47929
    if (!$is_https) {
      // Test that timeout is respected. The test machine is expected to be able
      // to make the connection (i.e. complete the fsockopen()) in 2 seconds and
      // return within a total of 5 seconds. If the test machine is extremely
      // slow, the test will fail. fsockopen() has been seen to time out in
      // slightly less than the specified timeout, so allow a little slack on
      // the minimum expected time (i.e. 1.8 instead of 2).
      timer_start(__METHOD__);
      $result = drupal_http_request(url('system-test/sleep/10', array('absolute' => TRUE)), array('timeout' => 2));
      $time = timer_read(__METHOD__) / 1000;
976
977
978
      $this->assertTrue(1.8 < $time && $time < 5, t('Request timed out (%time seconds).', array('%time' => $time)));
      $this->assertTrue($result->error, t('An error message was returned.'));
      $this->assertEqual($result->code, HTTP_REQUEST_TIMEOUT, t('Proper error code was returned.'));
979
    }
980
  }
981
982
983
984
985
986

  function testDrupalHTTPRequestBasicAuth() {
    $username = $this->randomName();
    $password = $this->randomName();
    $url = url('system-test/auth', array('absolute' => TRUE));

987
    $auth = str_replace('://', '://' . $username . ':' . $password . '@', $url);
988
989
    $result = drupal_http_request($auth);

990
    $this->drupalSetContent($result->data);
991
992
    $this->assertRaw($username, t('$_SERVER["PHP_AUTH_USER"] is passed correctly.'));
    $this->assertRaw($password, t('$_SERVER["PHP_AUTH_PW"] is passed correctly.'));
993
994
995
  }

  function testDrupalHTTPRequestRedirect() {
996
    $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 1));
997
    $this->assertEqual($redirect_301->redirect_code, 301, t('drupal_http_request follows the 301 redirect.'));
998

999
    $redirect_301 = drupal_http_request(url('system-test/redirect/301', array('absolute' => TRUE)), array('max_redirects' => 0));
1000
    $this->assertFalse(isset($redirect_301->redirect_code), t('drupal_http_request does not follow 301 redirect if max_redirects = 0.'));
For faster browsing, not all history is shown. View entire blame