diff --git a/src/Symfony/Components/BrowserKit/Client.php b/src/Symfony/Components/BrowserKit/Client.php new file mode 100644 index 0000000000..f5d635760f --- /dev/null +++ b/src/Symfony/Components/BrowserKit/Client.php @@ -0,0 +1,376 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Client simulates a browser. + * + * To make the actual request, you need to implement the doRequest() method. + * + * If you want to be able to run requests in their own process (insulated flag), + * you need to also implement the getScript() method. + * + * @package Symfony + * @subpackage Components_BrowserKit + * @author Fabien Potencier + */ +abstract class Client +{ + protected $history; + protected $cookieJar; + protected $server; + protected $request; + protected $response; + protected $crawler; + protected $insulated; + protected $redirect; + + /** + * Constructor. + * + * @param array $server The server parameters (equivalent of $_SERVER) + * @param Symfony\Components\BrowserKit\History $history A History instance to store the browser history + * @param Symfony\Components\BrowserKit\CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->setServerParameters($server); + $this->history = null === $history ? new History() : $history; + $this->cookieJar = null === $cookieJar ? new CookieJar() : $cookieJar; + $this->insulated = false; + } + + /** + * Sets the insulated flag. + * + * @param Boolean $insulated Whether to insulate the requests or not + */ + public function insulate($insulated = true) + { + if (!class_exists('Symfony\\Components\\Process\\Process')) + { + // @codeCoverageIgnoreStart + throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.'); + // @codeCoverageIgnoreEnd + } + + $this->insulated = (Boolean) $insulated; + } + + /** + * Sets server parameters. + * + * @param array $server An array of server parameters + */ + public function setServerParameters(array $server) + { + $this->server = array_merge(array( + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3', + ), $server); + } + + /** + * Returns the History instance. + * + * @return Symfony\Components\BrowserKit\History A History instance + */ + public function getHistory() + { + return $this->history; + } + + /** + * Returns the CookieJar instance. + * + * @return Symfony\Components\BrowserKit\CookieJar A CookieJar instance + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Returns the current Crawler instance. + * + * @return Symfony\Components\DomCrawler\Crawler A Crawler instance + */ + public function getCrawler() + { + return $this->crawler; + } + + /** + * Returns the current Response instance. + * + * @return Symfony\Components\BrowserKit\Response A Response instance + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns the current Request instance. + * + * @return Symfony\Components\BrowserKit\Request A Request instance + */ + public function getRequest() + { + return $this->request; + } + + /** + * Clicks on a given link. + * + * @param Symfony\Components\BrowserKit\Link $link A Link instance + */ + public function click(Link $link) + { + return $this->request($link->getMethod(), $link->getUri()); + } + + /** + * Submits a form. + * + * @param Symfony\Components\BrowserKit\Form $form A Form instance + * @param array $values An array of form field values + */ + public function submit(Form $form, array $values = array()) + { + $form->setValues($values); + + return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), array(), $form->getPhpFiles()); + } + + /** + * Calls a URI. + * + * @param string $method The request method + * @param string $uri The URI to fetch + * @param array $parameters The Request parameters + * @param array $headers The headers + * @param array $files The files + * @param array $server The server parameters + * @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Symfony\Components\DomCrawler\Crawler + */ + public function request($method, $uri, $parameters = array(), $headers = array(), $files = array(), $server = array(), $changeHistory = true) + { + $uri = $this->getAboluteUri($uri); + + $server = array_merge($this->server, $server); + if (!$this->history->isEmpty()) + { + $server['HTTP_REFERER'] = $this->history->current()->getUri(); + } + $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST); + $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); + + $request = new Request($uri, $method, $parameters, $files, $this->cookieJar->getValues($uri), $server); + + $this->request = $this->filterRequest($request); + + if (true === $changeHistory) + { + $this->history->add($request); + } + + if ($this->insulated) + { + $this->response = $this->doRequestInProcess($this->request); + } + else + { + $this->response = $this->doRequest($this->request); + } + + $response = $this->filterResponse($this->response); + + $this->cookieJar->updateFromResponse($response); + + $this->redirect = $response->getHeader('Location'); + + return $this->crawler = $this->createCrawlerFromContent($request->getUri(), $response->getContent(), $response->getHeader('Content-Type')); + } + + /** + * Makes a request in another process. + * + * @param Request $request A Request instance + * + * @param Response $response A Response instance + */ + protected function doRequestInProcess($request) + { + $process = new PhpProcess($this->getScript($request)); + $process->run(); + + if ($process->getExitCode() > 0) + { + throw new \RuntimeException($process->getErrorOutput()); + } + + return unserialize($process->getOutput()); + } + + /** + * Makes a request. + * + * @param Request $request A Request instance + * + * @param Response $response A Response instance + */ + abstract protected function doRequest($request); + + /** + * Returns the script to execute when the request must be insulated. + * + * @param Request $request A Request instance + */ + protected function getScript($request) + { + // @codeCoverageIgnoreStart + throw new \LogicException('To insulate requests, you need to override the getScript() method.'); + // @codeCoverageIgnoreEnd + } + + protected function filterRequest(Request $request) + { + return $request; + } + + protected function filterResponse($response) + { + return $response; + } + + protected function createCrawlerFromContent($uri, $content, $type) + { + $crawler = new Crawler(null, $uri); + $crawler->addContent($content, $type); + + return $crawler; + } + + /** + * Goes back in the browser history. + */ + public function back() + { + return $this->requestFromRequest($this->history->back(), false); + } + + /** + * Goes forward in the browser history. + */ + public function forward() + { + return $this->requestFromRequest($this->history->forward(), false); + } + + /** + * Reloads the current browser. + */ + public function reload() + { + return $this->requestFromRequest($this->history->current(), false); + } + + /** + * Follow redirects? + * + * @throws sfException If request was not a redirect + * + * @return Symfony\Components\BrowserKit\Client + */ + public function followRedirect() + { + if (null === $this->redirect) + { + throw new \LogicException('The request was not redirected.'); + } + + return $this->request('get', $this->redirect); + } + + /** + * Restarts the client. + * + * It flushes all cookies. + */ + public function restart() + { + $this->cookieJar->clear(); + $this->history->clear(); + } + + protected function getAboluteUri($uri) + { + // already absolute? + if ('http' === substr($uri, 0, 4)) + { + return $uri; + } + + if (!$this->history->isEmpty()) + { + $currentUri = $this->history->current()->getUri(); + } + else + { + $currentUri = sprintf('http%s://%s/', + isset($this->server['HTTPS']) ? 's' : '', + isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost' + ); + } + + // anchor? + if (!$uri || '#' == $uri[0]) + { + return preg_replace('/#.*?$/', '', $currentUri).$uri; + } + + if ('/' !== $uri[0]) + { + $path = parse_url($currentUri, PHP_URL_PATH); + + if ('/' !== substr($path, -1)) + { + $path = substr($path, 0, strrpos($path, '/') + 1); + } + + $uri = $path.$uri; + } + + return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri; + } + + /** + * Makes a request from a Request object directly. + * + * @param Symfony\Components\BrowserKit\Request $request A Request instance + * @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Symfony\Components\DomCrawler\Crawler + */ + protected function requestFromRequest(Request $request, $changeHistory = true) + { + return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), array(), $request->getServer(), $changeHistory); + } +} diff --git a/src/Symfony/Components/BrowserKit/Cookie.php b/src/Symfony/Components/BrowserKit/Cookie.php new file mode 100644 index 0000000000..94a6d505c3 --- /dev/null +++ b/src/Symfony/Components/BrowserKit/Cookie.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Cookie represents an HTTP cookie. + * + * @package Symfony + * @subpackage Components_BrowserKit + * @author Fabien Potencier + */ +class Cookie +{ + protected $name; + protected $value; + protected $expire; + protected $path; + protected $domain; + protected $secure; + + /** + * Sets a cookie. + * + * @param string $name The cookie name + * @param string $value The value of the cookie + * @param string $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available + * @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client + */ + public function __construct($name, $value, $expire = 0, $path = '/', $domain = '', $secure = false) + { + $this->name = $name; + $this->value = $value; + $this->expire = (integer) $expire; + $this->path = empty($path) ? '/' : $path; + $this->domain = $domain; + $this->secure = (Boolean) $secure; + } + + /** + * Gets the name of the cookie. + * + * @return string The cookie name + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string The cookie value + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the expire time of the cookie. + * + * @return string The cookie expire time + */ + public function getExpireTime() + { + return $this->expire; + } + + /** + * Gets the path of the cookie. + * + * @return string The cookie path + */ + public function getPath() + { + return $this->path; + } + + /** + * Gets the domain of the cookie. + * + * @return string The cookie domain + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Returns the secure flag of the cookie. + * + * @return Boolean The cookie secure flag + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Returns true if the cookie has expired. + * + * @return Boolean true if the cookie has expired, false otherwise + */ + public function isExpired() + { + return $this->expire && $this->expire < time(); + } +} diff --git a/src/Symfony/Components/BrowserKit/CookieJar.php b/src/Symfony/Components/BrowserKit/CookieJar.php new file mode 100644 index 0000000000..e8df98ac60 --- /dev/null +++ b/src/Symfony/Components/BrowserKit/CookieJar.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * CookieJar. + * + * @package Symfony + * @subpackage Components_BrowserKit + * @author Fabien Potencier + */ +class CookieJar +{ + protected $cookieJar = array(); + + /** + * Sets a cookie. + * + * @param Symfony\Components\BrowserKit\Cookie $cookie A Cookie instance + */ + public function set(Cookie $cookie) + { + $this->cookieJar[$cookie->getName()] = $cookie; + } + + /** + * Gets a cookie by name. + * + * @param string $name The cookie name + * + * @return Symfony\Components\BrowserKit\Cookie|null A Cookie instance or null if the cookie does not exist + */ + public function get($name) + { + $this->flushExpiredCookies(); + + return isset($this->cookieJar[$name]) ? $this->cookieJar[$name] : null; + } + + /** + * Removes a cookie by name. + * + * @param string $name The cookie name + */ + public function expire($name) + { + unset($this->cookieJar[$name]); + } + + /** + * Removes all the cookies from the jar. + */ + public function clear() + { + $this->cookieJar = array(); + } + + /** + * Updates the cookie jar from a Response object. + * + * @param Symfony\Components\BrowserKit\Response $response A Response object + */ + public function updateFromResponse(Response $response) + { + $this->clear(); + foreach ($response->getCookies() as $name => $cookie) + { + $this->set(new Cookie( + $name, + isset($cookie['value']) ? $cookie['value'] : '', + isset($cookie['expire']) ? $cookie['expire'] : 0, + isset($cookie['path']) ? $cookie['path'] : '/', + isset($cookie['domain']) ? $cookie['domain'] : '', + isset($cookie['secure']) ? $cookie['secure'] : false + )); + } + } + + /** + * Returns not yet expired cookies. + * + * @return array An array of cookies + */ + public function all() + { + $this->flushExpiredCookies(); + + return $this->cookieJar; + } + + /** + * Returns not yet expired cookie values for the given URI. + * + * @param string $uri A URI + * + * @return array An array of cookie values + */ + public function getValues($uri) + { + $this->flushExpiredCookies(); + + $parts = parse_url($uri); + + $cookies = array(); + foreach ($this->cookieJar as $cookie) + { + if ($cookie->getDomain() && $cookie->getDomain() != substr($parts['host'], -strlen($cookie->getDomain()))) + { + continue; + } + + if ($cookie->getPath() != substr($parts['path'], 0, strlen($cookie->getPath()))) + { + continue; + } + + if ($cookie->isSecure() && 'https' != $parts['scheme']) + { + continue; + } + + $cookies[$cookie->getName()] = $cookie->getValue(); + } + + return $cookies; + } + + /** + * Removes all expired cookies. + */ + public function flushExpiredCookies() + { + $cookies = $this->cookieJar; + foreach ($cookies as $name => $cookie) + { + if ($cookie->isExpired()) + { + unset($this->cookieJar[$name]); + } + } + } +} diff --git a/src/Symfony/Components/BrowserKit/History.php b/src/Symfony/Components/BrowserKit/History.php new file mode 100644 index 0000000000..ed232d9ddd --- /dev/null +++ b/src/Symfony/Components/BrowserKit/History.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * History. + * + * @package Symfony + * @subpackage Components_BrowserKit + * @author Fabien Potencier + */ +class History +{ + protected $stack = array(); + protected $position = -1; + + /** + * Constructor. + */ + public function __construct() + { + $this->clear(); + } + + /** + * Clears the history. + */ + public function clear() + { + $this->stack = array(); + $this->position = -1; + } + + /** + * Adds a Request to the history. + * + * @param Symfony\Components\BrowserKit\Request $request A Request instance + */ + public function add(Request $request) + { + $this->stack = array_slice($this->stack, 0, $this->position + 1); + $this->stack[] = clone $request; + $this->position = count($this->stack) - 1; + } + + /** + * Returns true if the history is empty. + * + * @return Boolean true if the history is empty, false otherwise + */ + public function isEmpty() + { + return count($this->stack) == 0; + } + + /** + * Goes back in the history. + * + * @return Symfony\Components\BrowserKit\Request A Request instance + * + * @throws \LogicException if the stack is already on the first page + */ + public function back() + { + if ($this->position < 1) + { + throw new \LogicException('You are already on the first page.'); + } + + return clone $this->stack[--$this->position]; + } + + /** + * Goes forward in the history. + * + * @return Symfony\Components\BrowserKit\Request A Request instance + * + * @throws \LogicException if the stack is already on the last page + */ + public function forward() + { + if ($this->position > count($this->stack) - 2) + { + throw new \LogicException('You are already on the last page.'); + } + + return clone $this->stack[++$this->position]; + } + + /** + * Returns the current element in the history. + * + * @return Symfony\Components\BrowserKit\Request A Request instance + * + * @throws \LogicException if the stack is empty + */ + public function current() + { + if (-1 == $this->position) + { + throw new \LogicException('The page history is empty.'); + } + + return clone $this->stack[$this->position]; + } +} diff --git a/src/Symfony/Components/BrowserKit/Request.php b/src/Symfony/Components/BrowserKit/Request.php new file mode 100644 index 0000000000..bcdd4e6066 --- /dev/null +++ b/src/Symfony/Components/BrowserKit/Request.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Request object. + * + * @package Symfony + * @subpackage Components_BrowserKit + * @author Fabien Potencier + */ +class Request +{ + protected $uri; + protected $method; + protected $parameters; + protected $files; + protected $cookies; + protected $server; + + /** + * Constructor. + * + * @param string $uri The request URI + * @param array $parameters The request parameters + * @param array $files An array of uploaded files + * @param array $cookies An array of cookies + * @param array $server An array of server parameters + */ + public function __construct($uri, $method, array $parameters = array(), array $files = array(), array $cookies = array(), array $server = array()) + { + $this->uri = $uri; + $this->method = $method; + $this->parameters = $parameters; + $this->files = $files; + $this->cookies = $cookies; + $this->server = $server; + } + + /** + * Gets the request URI. + * + * @return string The request URI + */ + public function getUri() + { + return $this->uri; + } + + /** + * Gets the request HTTP method. + * + * @return string The request HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the request parameters. + * + * @return array The request parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets the request server files. + * + * @return array The request files + */ + public function getFiles() + { + return $this->files; + } + + /** + * Gets the request cookies. + * + * @return array The request cookies + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Gets the request server parameters. + * + * @return array The request server parameters + */ + public function getServer() + { + return $this->server; + } +} diff --git a/src/Symfony/Components/BrowserKit/Response.php b/src/Symfony/Components/BrowserKit/Response.php new file mode 100644 index 0000000000..a3bb7bc8d8 --- /dev/null +++ b/src/Symfony/Components/BrowserKit/Response.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Response object. + * + * @package Symfony + * @subpackage Components_BrowserKit + * @author Fabien Potencier + */ +class Response +{ + protected $content; + protected $status; + protected $headers; + protected $cookies; + + /** + * Constructor. + * + * @param string $content The content of the response + * @param integer $status The response status code + * @param array $headers An array of headers + * @param array $cookies An array of cookies + */ + public function __construct($content = '', $status = 200, $headers = array(), $cookies = array()) + { + $this->content = $content; + $this->status = $status; + $this->headers = $headers; + $this->cookies = $cookies; + } + + /** + * Gets the response content. + * + * @return string The response content + */ + public function getContent() + { + return $this->content; + } + + /** + * Gets the response status code. + * + * @return integer The response status code + */ + public function getStatus() + { + return $this->status; + } + + /** + * Gets the response headers. + * + * @return array The response headers + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Gets a response header. + * + * @param string $header The header name + * + * @return string The header value + */ + public function getHeader($header) + { + foreach ($this->headers as $key => $value) + { + if (str_replace('-', '_', strtolower($key)) == str_replace('-', '_', strtolower($header))) + { + return $value; + } + } + + return null; + } + + /** + * Gets the response cookies. + * + * @return array The response cookies + */ + public function getCookies() + { + return $this->cookies; + } +} diff --git a/tests/Symfony/Tests/Components/BrowserKit/ClientTest.php b/tests/Symfony/Tests/Components/BrowserKit/ClientTest.php new file mode 100644 index 0000000000..a9de6875db --- /dev/null +++ b/tests/Symfony/Tests/Components/BrowserKit/ClientTest.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\BrowserKit; + +use Symfony\Components\BrowserKit\Client; +use Symfony\Components\BrowserKit\History; +use Symfony\Components\BrowserKit\CookieJar; +use Symfony\Components\BrowserKit\Request; +use Symfony\Components\BrowserKit\Response; + +class TestClient extends Client +{ + protected $nextResponse = null; + protected $nextScript = null; + + public function setNextResponse(Response $response) + { + $this->nextResponse = $response; + } + + public function setNextScript($script) + { + $this->nextScript = $script; + } + + protected function doRequest($request) + { + if (null === $this->nextResponse) + { + return new Response(); + } + else + { + $response = $this->nextResponse; + $this->nextResponse = null; + + return $response; + } + } + + protected function getScript($request) + { + $r = new \ReflectionClass('Symfony\Components\BrowserKit\Response'); + $path = $r->getFileName(); + + return <<nextScript); +EOF; + } +} + +class ClientTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Components\BrowserKit\Client::getHistory + */ + public function testGetHistory() + { + $client = new TestClient(array(), $history = new History()); + $this->assertSame($history, $client->getHistory(), '->getHistory() returns the History'); + } + + /** + * @covers Symfony\Components\BrowserKit\Client::getCookieJar + */ + public function testGetCookieJar() + { + $client = new TestClient(array(), null, $cookieJar = new CookieJar()); + $this->assertSame($cookieJar, $client->getCookieJar(), '->getCookieJar() returns the CookieJar'); + } + + /** + * @covers Symfony\Components\BrowserKit\Client::getRequest + */ + public function testGetRequest() + { + $client = new TestClient(); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('http://example.com/', $client->getRequest()->getUri(), '->getCrawler() returns the Request of the last request'); + } + + /** + * @covers Symfony\Components\BrowserKit\Client::getResponse + */ + public function testGetResponse() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request'); + } + + /** + * @covers Symfony\Components\BrowserKit\Client::getCrawler + */ + public function testGetCrawler() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://example.com/'); + + $this->assertSame($crawler, $client->getCrawler(), '->getCrawler() returns the Crawler of the last request'); + } + + public function testRequestHttpHeaders() + { + $client = new TestClient(); + $client->request('GET', '/'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('localhost', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('www.example.com', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client->request('GET', 'https://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertTrue($headers['HTTPS'], '->request() sets the HTTPS header'); + } + + public function testRequestURIConversion() + { + $client = new TestClient(); + $client->request('GET', '/foo'); + $this->assertEquals('http://localhost/foo', $client->getRequest()->getUri(), '->request() converts the URI to an absolute one'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $this->assertEquals('http://www.example.com', $client->getRequest()->getUri(), '->request() does not change absolute URIs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/'); + $client->request('GET', '/foo'); + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#foo'); + $this->assertEquals('http://www.example.com/foo#foo', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + } + + public function testRequestReferer() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $server = $client->getRequest()->getServer(); + $this->assertEquals('http://www.example.com/foo/foobar', $server['HTTP_REFERER'], '->request() sets the referer'); + } + + public function testRequestHistory() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + + $this->assertEquals('http://www.example.com/foo/bar', $client->getHistory()->current()->getUri(), '->request() updates the History'); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getHistory()->back()->getUri(), '->request() updates the History'); + } + + public function testRequestCookies() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); +RETURN; + $this->assertEquals('http://www.example.com/foo/bar', $client->getHistory()->current()->getUri(), '->request() updates the History'); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getHistory()->back()->getUri(), '->request() updates the History'); + } + + public function testClick() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('a')->link()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() clicks on links'); + } + + public function testSubmit() + { + $client = new TestClient(); + $client->setNextResponse(new Response('
')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + } + + public function testFollowRedirect() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try + { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was not redirected'); + } + catch (\Exception $e) + { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request was not redirected'); + } + + $client->setNextResponse(new Response('', 200, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->followRedirect(); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + } + + public function testBack() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'http://www.example.com/foo'); + $client->back(); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->back() goes back in the history'); + } + + public function testForward() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'http://www.example.com/foo'); + $client->back(); + $client->forward(); + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->forward() goes forward in the history'); + } + + public function testReload() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->reload(); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->forward() realoads the current page'); + } + + public function testRestart() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->restart(); + + $this->assertTrue($client->getHistory()->isEmpty(), '->restart() clears the history'); + $this->assertEquals(array(), $client->getCookieJar()->all(), '->restart() clears the cookies'); + } + + public function testInsulatedRequests() + { + $client = new TestClient(); + $client->insulate(); + $client->setNextScript("new Symfony\Components\BrowserKit\Response('foobar')"); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('foobar', $client->getResponse()->getContent(), '->insulate() process the request in a forked process'); + + $client->setNextScript("new Symfony\Components\BrowserKit\Response('foobar)"); + + try + { + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->fail('->request() throws a \RuntimeException if the script has an error'); + } + catch (\Exception $e) + { + $this->assertInstanceof('RuntimeException', $e, '->request() throws a \RuntimeException if the script has an error'); + } + } +} diff --git a/tests/Symfony/Tests/Components/BrowserKit/CookieJarTest.php b/tests/Symfony/Tests/Components/BrowserKit/CookieJarTest.php new file mode 100644 index 0000000000..195e30071b --- /dev/null +++ b/tests/Symfony/Tests/Components/BrowserKit/CookieJarTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\BrowserKit; + +use Symfony\Components\BrowserKit\CookieJar; +use Symfony\Components\BrowserKit\Cookie; +use Symfony\Components\BrowserKit\Response; + +class CookieJarTest extends \PHPUnit_Framework_TestCase +{ + public function testSetGet() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + + $this->assertEquals($cookie, $cookieJar->get('foo'), '->set() sets a cookie'); + + $this->assertNull($cookieJar->get('foobar'), '->get() returns null if the cookie does not exist'); + + $cookieJar->set($cookie = new Cookie('foo', 'bar', time() - 86400)); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testExpire() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + $cookieJar->expire('foo'); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testAll() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $this->assertEquals(array('foo' => $cookie1, 'bar' => $cookie2), $cookieJar->all(), '->all() returns all cookies in the jar'); + } + + public function testClear() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $cookieJar->clear(); + + $this->assertEquals(array(), $cookieJar->all(), '->clear() expires all cookies'); + } + + public function testUpdateFromResponse() + { + $response = new Response('', 200, array(), array('foo' => array('value' => 'foo'))); + + $cookieJar = new CookieJar(); + $cookieJar->set(new Cookie('bar', 'bar')); + $cookieJar->updateFromResponse($response); + + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromResponse() updates cookies from a Response objects'); + $this->assertNull($cookieJar->get('bar'), '->updateFromResponse() updates cookies from a Response objects'); + } + + /** + * @dataProvider provideGetValuesValues + */ + public function testGetValues($uri, $values) + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo_nothing', 'foo')); + $cookieJar->set($cookie2 = new Cookie('foo_expired', 'foo', time() - 86400)); + $cookieJar->set($cookie3 = new Cookie('foo_path', 'foo', 0, '/foo')); + $cookieJar->set($cookie4 = new Cookie('foo_domain', 'foo', 0, '/', 'example.com')); + $cookieJar->set($cookie5 = new Cookie('foo_secure', 'foo', 0, '/', '', true)); + + $this->assertEquals($values, array_keys($cookieJar->getValues($uri)), '->getValues() returns the cookie for a given URI'); + } + + public function provideGetValuesValues() + { + return array( + array('http://www.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example1.com/', array('foo_nothing')), + array('https://foo.example.com/', array('foo_nothing', 'foo_domain', 'foo_secure')), + array('http://www.example.com/foo/bar', array('foo_nothing', 'foo_path', 'foo_domain')), + ); + } +} diff --git a/tests/Symfony/Tests/Components/BrowserKit/CookieTest.php b/tests/Symfony/Tests/Components/BrowserKit/CookieTest.php new file mode 100644 index 0000000000..69e337b986 --- /dev/null +++ b/tests/Symfony/Tests/Components/BrowserKit/CookieTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\BrowserKit; + +use Symfony\Components\BrowserKit\Cookie; + +class CookieTest extends \PHPUnit_Framework_TestCase +{ + public function testGetName() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('foo', $cookie->getName(), '->getName() returns the cookie name'); + } + + public function testGetValue() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('bar', $cookie->getValue(), '->getValue() returns the cookie value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar', 0); + $this->assertEquals('/', $cookie->getPath(), '->getPath() returns / is no path is defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/foo'); + $this->assertEquals('/foo', $cookie->getPath(), '->getPath() returns the cookie path'); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com'); + $this->assertEquals('foo.com', $cookie->getDomain(), '->getDomain() returns the cookie domain'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isSecure(), '->isSecure() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', true); + $this->assertTrue($cookie->isSecure(), '->getDomain() returns the cookie secure flag'); + } + + public function testGetExpireTime() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals(0, $cookie->getExpireTime(), '->getExpireTime() returns the expire time'); + + $cookie = new Cookie('foo', 'bar', $time = time() - 86400); + $this->assertEquals($time, $cookie->getExpireTime(), '->getExpireTime() returns the expire time'); + } + + public function testIsExpired() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isExpired(), '->isExpired() returns false when the cookie never expires (0 as expire time)'); + + $cookie = new Cookie('foo', 'bar', time() - 86400); + $this->assertTrue($cookie->isExpired(), '->isExpired() returns true when the cookie is expired'); + } +} diff --git a/tests/Symfony/Tests/Components/BrowserKit/HistoryTest.php b/tests/Symfony/Tests/Components/BrowserKit/HistoryTest.php new file mode 100644 index 0000000000..a154e4f728 --- /dev/null +++ b/tests/Symfony/Tests/Components/BrowserKit/HistoryTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\BrowserKit; + +use Symfony\Components\BrowserKit\History; +use Symfony\Components\BrowserKit\Request; + +class HistoryTest extends \PHPUnit_Framework_TestCase +{ + public function testAdd() + { + $history = new History(); + $history->add(new Request('http://www.example1.com/', 'get')); + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example2.com/', 'get')); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example3.com/', 'get')); + $history->back(); + $history->add(new Request('http://www.example4.com/', 'get')); + $this->assertSame('http://www.example4.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->back(); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + } + + public function testClearIsEmpty() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertFalse($history->isEmpty(), '->isEmpty() returns false if the history is not empty'); + + $history->clear(); + + $this->assertTrue($history->isEmpty(), '->isEmpty() true if the history is empty'); + } + + public function testCurrent() + { + $history = new History(); + + try + { + $history->current(); + $this->fail('->current() throws a \LogicException if the history is empty'); + } + catch (\Exception $e) + { + $this->assertInstanceof('LogicException', $e, '->current() throws a \LogicException if the history is empty'); + } + + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->current() returns the current request in the history'); + } + + public function testBack() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + try + { + $history->back(); + $this->fail('->back() throws a \LogicException if the history is already on the first page'); + } + catch (\Exception $e) + { + $this->assertInstanceof('LogicException', $e, '->current() throws a \LogicException if the history is already on the first page'); + } + + $history->add(new Request('http://www.example1.com/', 'get')); + $history->back(); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->back() returns the previous request in the history'); + } + + public function testForward() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + $history->add(new Request('http://www.example1.com/', 'get')); + + try + { + $history->forward(); + $this->fail('->forward() throws a \LogicException if the history is already on the last page'); + } + catch (\Exception $e) + { + $this->assertInstanceof('LogicException', $e, '->forward() throws a \LogicException if the history is already on the last page'); + } + + $history->back(); + $history->forward(); + + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->forward() returns the next request in the history'); + } +} diff --git a/tests/Symfony/Tests/Components/BrowserKit/RequestTest.php b/tests/Symfony/Tests/Components/BrowserKit/RequestTest.php new file mode 100644 index 0000000000..673b6367ae --- /dev/null +++ b/tests/Symfony/Tests/Components/BrowserKit/RequestTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\BrowserKit; + +use Symfony\Components\BrowserKit\Request; + +class RequestTest extends \PHPUnit_Framework_TestCase +{ + public function testGetUri() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('http://www.example.com/', $request->getUri(), '->getUri() returns the URI of the request'); + } + + public function testGetMethod() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('get', $request->getMethod(), '->getMethod() returns the method of the request'); + } + + public function testGetParameters() + { + $request = new Request('http://www.example.com/', 'get', array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getParameters(), '->getParameters() returns the parameters of the request'); + } + + public function testGetFiles() + { + $request = new Request('http://www.example.com/', 'get', array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getFiles(), '->getFiles() returns the uploaded files of the request'); + } + + public function testGetCookies() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getCookies(), '->getCookies() returns the cookies of the request'); + } + + public function testGetServer() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getServer(), '->getServer() returns the server parameters of the request'); + } +} diff --git a/tests/Symfony/Tests/Components/BrowserKit/ResponseTest.php b/tests/Symfony/Tests/Components/BrowserKit/ResponseTest.php new file mode 100644 index 0000000000..b169727c3e --- /dev/null +++ b/tests/Symfony/Tests/Components/BrowserKit/ResponseTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Components\BrowserKit; + +use Symfony\Components\BrowserKit\Response; + +class ResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testGetUri() + { + $response = new Response('foo'); + $this->assertEquals('foo', $response->getContent(), '->getContent() returns the content of the response'); + } + + public function testGetStatus() + { + $response = new Response('foo', 304); + $this->assertEquals('304', $response->getStatus(), '->getStatus() returns the status of the response'); + } + + public function testGetHeaders() + { + $response = new Response('foo', 304, array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $response->getHeaders(), '->getHeaders() returns the headers of the response'); + } + + public function testGetHeader() + { + $response = new Response('foo', 304, array('Content-Type' => 'text/html')); + + $this->assertEquals('text/html', $response->getHeader('Content-Type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content-type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content_type'), '->getHeader() returns a header of the response'); + + $this->assertNull($response->getHeader('foo'), '->getHeader() returns null if the header is not defined'); + } + + public function testGetCookies() + { + $response = new Response('foo', 304, array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $response->getCookies(), '->getCookies() returns the cookies of the response'); + } +}