Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
/*
4
 * This file is part of the symfony package.
5
 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
 
11
/**
12
 * sfTesterResponse implements tests for the symfony response object.
13
 *
14
 * @package    symfony
15
 * @subpackage test
16
 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17
 * @version    SVN: $Id: sfTesterResponse.class.php 27061 2010-01-22 17:08:04Z FabianLange $
18
 */
19
class sfTesterResponse extends sfTester
20
{
21
  protected
22
    $response       = null,
23
    $dom            = null,
24
    $domCssSelector = null;
25
 
26
  /**
27
   * Prepares the tester.
28
   */
29
  public function prepare()
30
  {
31
  }
32
 
33
  /**
34
   * Initializes the tester.
35
   */
36
  public function initialize()
37
  {
38
    $this->response = $this->browser->getResponse();
39
 
40
    $this->dom = null;
41
    $this->domCssSelector = null;
42
    if (preg_match('/(x|ht)ml/i', $this->response->getContentType(), $matches))
43
    {
44
      $this->dom = new DOMDocument('1.0', $this->response->getCharset());
45
      $this->dom->validateOnParse = true;
46
      if ('x' == $matches[1])
47
      {
48
        @$this->dom->loadXML($this->response->getContent());
49
      }
50
      else
51
      {
52
        @$this->dom->loadHTML($this->response->getContent());
53
      }
54
      $this->domCssSelector = new sfDomCssSelector($this->dom);
55
    }
56
  }
57
 
58
  /**
59
   * Tests that the response matches a given CSS selector.
60
   *
61
   * @param  string $selector  The response selector or a sfDomCssSelector object
62
   * @param  mixed  $value     Flag for the selector
63
   * @param  array  $options   Options for the current test
64
   *
65
   * @return sfTestFunctionalBase|sfTester
66
   */
67
  public function checkElement($selector, $value = true, $options = array())
68
  {
69
    if (null === $this->dom)
70
    {
71
      throw new LogicException('The DOM is not accessible because the browser response content type is not HTML.');
72
    }
73
 
74
    if (is_object($selector))
75
    {
76
      $values = $selector->getValues();
77
    }
78
    else
79
    {
80
      $values = $this->domCssSelector->matchAll($selector)->getValues();
81
    }
82
 
83
    if (false === $value)
84
    {
85
      $this->tester->is(count($values), 0, sprintf('response selector "%s" does not exist', $selector));
86
    }
87
    else if (true === $value)
88
    {
89
      $this->tester->cmp_ok(count($values), '>', 0, sprintf('response selector "%s" exists', $selector));
90
    }
91
    else if (is_int($value))
92
    {
93
      $this->tester->is(count($values), $value, sprintf('response selector "%s" matches "%s" times', $selector, $value));
94
    }
95
    else if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $value, $match))
96
    {
97
      $position = isset($options['position']) ? $options['position'] : 0;
98
      if ($match[1] == '!')
99
      {
100
        $this->tester->unlike(@$values[$position], substr($value, 1), sprintf('response selector "%s" does not match regex "%s"', $selector, substr($value, 1)));
101
      }
102
      else
103
      {
104
        $this->tester->like(@$values[$position], $value, sprintf('response selector "%s" matches regex "%s"', $selector, $value));
105
      }
106
    }
107
    else
108
    {
109
      $position = isset($options['position']) ? $options['position'] : 0;
110
      $this->tester->is(@$values[$position], $value, sprintf('response selector "%s" matches "%s"', $selector, $value));
111
    }
112
 
113
    if (isset($options['count']))
114
    {
115
      $this->tester->is(count($values), $options['count'], sprintf('response selector "%s" matches "%s" times', $selector, $options['count']));
116
    }
117
 
118
    return $this->getObjectToReturn();
119
  }
120
 
121
  /**
122
   * Checks that a form is rendered correctly.
123
   *
124
   * @param  sfForm|string $form     A form object or the name of a form class
125
   * @param  string        $selector CSS selector for the root form element for this form
126
   *
127
   * @return sfTestFunctionalBase|sfTester
128
   */
129
  public function checkForm($form, $selector = 'form')
130
  {
131
    if (!$form instanceof sfForm)
132
    {
133
      $form = new $form();
134
    }
135
 
136
    $rendered = array();
137
    foreach ($this->domCssSelector->matchAll(sprintf('%1$s input, %1$s textarea, %1$s select', $selector))->getNodes() as $element)
138
    {
139
      $rendered[] = $element->getAttribute('name');
140
    }
141
 
142
    foreach ($form as $field => $widget)
143
    {
144
      $dom = new DOMDocument('1.0', sfConfig::get('sf_charset'));
145
      $dom->loadHTML((string) $widget);
146
 
147
      foreach ($dom->getElementsByTagName('*') as $element)
148
      {
149
        if (in_array($element->tagName, array('input', 'select', 'textarea')))
150
        {
151
          if (false !== $pos = array_search($element->getAttribute('name'), $rendered))
152
          {
153
            unset($rendered[$pos]);
154
          }
155
 
156
          $this->tester->ok(false !== $pos, sprintf('response includes "%s" form "%s" field - "%s %s[name=%s]"', get_class($form), $field, $selector, $element->tagName, $element->getAttribute('name')));
157
        }
158
      }
159
    }
160
 
161
    return $this->getObjectToReturn();
162
  }
163
 
164
  /**
165
   * Validates the response.
166
   *
167
   * @param mixed $checkDTD Either true to validate against the response DTD or
168
   *                        provide the path to a *.xsd, *.rng or *.rnc schema
169
   *
170
   * @return sfTestFunctionalBase|sfTester
171
   *
172
   * @throws LogicException If the response is neither XML nor (X)HTML
173
   */
174
  public function isValid($checkDTD = false)
175
  {
176
    if (preg_match('/(x|ht)ml/i', $this->response->getContentType()))
177
    {
178
      $revert = libxml_use_internal_errors(true);
179
 
180
      $dom = new DOMDocument('1.0', $this->response->getCharset());
181
      $content = $this->response->getContent();
182
 
183
      if (true === $checkDTD)
184
      {
185
        $cache = sfConfig::get('sf_cache_dir').'/sf_tester_response/w3';
186
        if ($cache[1] == ':')
187
        {
188
          // On Windows systems the path will be like c:\symfony\cache\xml.dtd
189
          // I did not manage to get DOMDocument loading a file protocol url including the drive letter
190
          // file://c:\symfony\cache\xml.dtd or file://c:/symfony/cache/xml.dtd
191
          // The first one simply doesnt work, the second one is treated as remote call.
192
          // However the following works. Unfortunatly this means we can only access the current disk
193
          // file:///symfony/cache/xml.dtd
194
          // Note that all work for file_get_contents so the bug is most likely in DOMDocument.
195
          $local = 'file://'.substr(str_replace(DIRECTORY_SEPARATOR, '/', $cache), 2);
196
        }
197
        else
198
        {
199
          $local = 'file://'.$cache;
200
        }
201
 
202
        if (!file_exists($cache.'/TR/xhtml11/DTD/xhtml11.dtd'))
203
        {
204
          $filesystem = new sfFilesystem();
205
 
206
          $finder = sfFinder::type('any')->discard('.sf');
207
          $filesystem->mirror(dirname(__FILE__).'/w3', $cache, $finder);
208
 
209
          $finder = sfFinder::type('file');
210
          $filesystem->replaceTokens($finder->in($cache), '##', '##', array('LOCAL_W3' => $local));
211
        }
212
 
213
        $content = preg_replace('#(<!DOCTYPE[^>]+")http://www.w3.org(.*")#i', '\\1'.$local.'\\2', $content);
214
        $dom->validateOnParse = $checkDTD;
215
      }
216
 
217
      $dom->loadXML($content);
218
 
219
      switch (pathinfo($checkDTD, PATHINFO_EXTENSION))
220
      {
221
        case 'xsd':
222
          $dom->schemaValidate($checkDTD);
223
          $message = sprintf('response validates per XSD schema "%s"', basename($checkDTD));
224
          break;
225
        case 'rng':
226
        case 'rnc':
227
          $dom->relaxNGValidate($checkDTD);
228
          $message = sprintf('response validates per relaxNG schema "%s"', basename($checkDTD));
229
          break;
230
        default:
231
          $message = $dom->validateOnParse ? sprintf('response validates as "%s"', $dom->doctype->name) : 'response is well-formed "xml"';
232
      }
233
 
234
      if (count($errors = libxml_get_errors()))
235
      {
236
        $lines = explode(PHP_EOL, $this->response->getContent());
237
 
238
        $this->tester->fail($message);
239
        foreach ($errors as $error)
240
        {
241
          $this->tester->diag('    '.trim($error->message));
242
          if (preg_match('/line (\d+)/', $error->message, $match) && $error->line != $match[1])
243
          {
244
            $this->tester->diag('      '.str_pad($match[1].':', 6).trim($lines[$match[1] - 1]));
245
          }
246
          $this->tester->diag('      '.str_pad($error->line.':', 6).trim($lines[$error->line - 1]));
247
        }
248
      }
249
      else
250
      {
251
        $this->tester->pass($message);
252
      }
253
 
254
      libxml_use_internal_errors($revert);
255
    }
256
    else
257
    {
258
      throw new LogicException(sprintf('Unable to validate responses of content type "%s"', $this->response->getContentType()));
259
    }
260
 
261
    return $this->getObjectToReturn();
262
  }
263
 
264
  /**
265
   * Tests for a response header.
266
   *
267
   * @param  string $key
268
   * @param  string $value
269
   *
270
   * @return sfTestFunctionalBase|sfTester
271
   */
272
  public function isHeader($key, $value)
273
  {
274
    $headers = explode(', ', $this->response->getHttpHeader($key));
275
    $ok = false;
276
    $regex = false;
277
    $mustMatch = true;
278
    if (preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $value, $match))
279
    {
280
      $regex = $value;
281
      if ($match[1] == '!')
282
      {
283
        $mustMatch = false;
284
        $regex = substr($value, 1);
285
      }
286
    }
287
 
288
    foreach ($headers as $header)
289
    {
290
      if (false !== $regex)
291
      {
292
        if ($mustMatch)
293
        {
294
          if (preg_match($regex, $header))
295
          {
296
            $ok = true;
297
            $this->tester->pass(sprintf('response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHttpHeader($key)));
298
            break;
299
          }
300
        }
301
        else
302
        {
303
          if (preg_match($regex, $header))
304
          {
305
            $ok = true;
306
            $this->tester->fail(sprintf('response header "%s" does not match "%s" (%s)', $key, $value, $this->response->getHttpHeader($key)));
307
            break;
308
          }
309
        }
310
      }
311
      else if ($header == $value)
312
      {
313
        $ok = true;
314
        $this->tester->pass(sprintf('response header "%s" is "%s" (%s)', $key, $value, $this->response->getHttpHeader($key)));
315
        break;
316
      }
317
    }
318
 
319
    if (!$ok)
320
    {
321
      if (!$mustMatch)
322
      {
323
        $this->tester->pass(sprintf('response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHttpHeader($key)));
324
      }
325
      else
326
      {
327
        $this->tester->fail(sprintf('response header "%s" matches "%s" (%s)', $key, $value, $this->response->getHttpHeader($key)));
328
      }
329
    }
330
 
331
    return $this->getObjectToReturn();
332
  }
333
 
334
  /**
335
   * Tests if a cookie was set.
336
   *
337
   * @param  string $name
338
   * @param  string $value
339
   * @param  array  $attributes Other cookie attributes to check (expires, path, domain, etc)
340
   *
341
   * @return sfTestFunctionalBase|sfTester
342
   */
343
  public function setsCookie($name, $value = null, $attributes = array())
344
  {
345
    foreach ($this->response->getCookies() as $cookie)
346
    {
347
      if ($name == $cookie['name'])
348
      {
349
        if (null === $value)
350
        {
351
          $this->tester->pass(sprintf('response sets cookie "%s"', $name));
352
        }
353
        else
354
        {
355
          $this->tester->ok($value == $cookie['value'], sprintf('response sets cookie "%s" to "%s"', $name, $value));
356
        }
357
 
358
        foreach ($attributes as $attributeName => $attributeValue)
359
        {
360
          if (!array_key_exists($attributeName, $cookie))
361
          {
362
            throw new LogicException(sprintf('The cookie attribute "%s" is not valid.', $attributeName));
363
          }
364
 
365
          $this->tester->is($cookie[$attributeName], $attributeValue, sprintf('"%s" cookie "%s" attribute is "%s"', $name, $attributeName, $attributeValue));
366
        }
367
 
368
        return $this->getObjectToReturn();
369
      }
370
    }
371
 
372
    $this->tester->fail(sprintf('response sets cookie "%s"', $name));
373
 
374
    return $this->getObjectToReturn();
375
  }
376
 
377
  /**
378
   * Tests the response content against a regex.
379
   *
380
   * @param string Regex
381
   *
382
   * @return sfTestFunctionalBase|sfTester
383
   */
384
  public function matches($regex)
385
  {
386
    if (!preg_match('/^(!)?([^a-zA-Z0-9\\\\]).+?\\2[ims]?$/', $regex, $match))
387
    {
388
      throw new InvalidArgumentException(sprintf('"%s" is not a valid regular expression.', $regex));
389
    }
390
 
391
    if ($match[1] == '!')
392
    {
393
      $this->tester->unlike($this->response->getContent(), substr($regex, 1), sprintf('response content does not match regex "%s"', substr($regex, 1)));
394
    }
395
    else
396
    {
397
      $this->tester->like($this->response->getContent(), $regex, sprintf('response content matches regex "%s"', $regex));
398
    }
399
 
400
    return $this->getObjectToReturn();
401
  }
402
 
403
  /**
404
   * Tests the status code.
405
   *
406
   * @param string $statusCode Status code to check, default 200
407
   *
408
   * @return sfTestFunctionalBase|sfTester
409
   */
410
  public function isStatusCode($statusCode = 200)
411
  {
412
    $this->tester->is($this->response->getStatusCode(), $statusCode, sprintf('status code is "%s"', $statusCode));
413
 
414
    return $this->getObjectToReturn();
415
  }
416
 
417
  /**
418
   * Tests if the current request has been redirected.
419
   *
420
   * @param  bool $boolean  Flag for redirection mode
421
   *
422
   * @return sfTestFunctionalBase|sfTester
423
   */
424
  public function isRedirected($boolean = true)
425
  {
426
    if ($location = $this->response->getHttpHeader('location'))
427
    {
428
      $boolean ? $this->tester->pass(sprintf('page redirected to "%s"', $location)) : $this->tester->fail(sprintf('page redirected to "%s"', $location));
429
    }
430
    else
431
    {
432
      $boolean ? $this->tester->fail('page redirected') : $this->tester->pass('page not redirected');
433
    }
434
 
435
    return $this->getObjectToReturn();
436
  }
437
 
438
  /**
439
   * Outputs some debug information about the current response.
440
   *
441
   * @param string $realOutput Whether to display the actual content of the response when an error occurred
442
   *                           or the exception message and the stack trace to ease debugging
443
   */
444
  public function debug($realOutput = false)
445
  {
446
    print $this->tester->error('Response debug');
447
 
448
    if (!$realOutput && null !== sfException::getLastException())
449
    {
450
      // print the exception and the stack trace instead of the "normal" output
451
      $this->tester->comment('WARNING');
452
      $this->tester->comment('An error occurred when processing this request.');
453
      $this->tester->comment('The real response content has been replaced with the exception message to ease debugging.');
454
    }
455
 
456
    printf("HTTP/1.X %s\n", $this->response->getStatusCode());
457
 
458
    foreach ($this->response->getHttpHeaders() as $name => $value)
459
    {
460
      printf("%s: %s\n", $name, $value);
461
    }
462
 
463
    foreach ($this->response->getCookies() as $cookie)
464
    {
465
      vprintf("Set-Cookie: %s=%s; %spath=%s%s%s%s\n", array(
466
        $cookie['name'],
467
        $cookie['value'],
468
        null === $cookie['expire'] ? '' : sprintf('expires=%s; ', date('D d-M-Y H:i:s T', $cookie['expire'])),
469
        $cookie['path'],
470
        $cookie['domain'] ? sprintf('; domain=%s', $cookie['domain']) : '',
471
        $cookie['secure'] ? '; secure' : '',
472
        $cookie['httpOnly'] ? '; HttpOnly' : '',
473
      ));
474
    }
475
 
476
    echo "\n";
477
    if (!$realOutput && null !== $exception = sfException::getLastException())
478
    {
479
      echo $exception;
480
    }
481
    else
482
    {
483
      echo $this->response->getContent();
484
    }
485
    echo "\n";
486
 
487
    exit(1);
488
  }
489
}