From 02b910d0f2c6f10a00fb09a7e08daec9e3e367fb Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Thu, 19 Sep 2013 23:15:56 -0700 Subject: [PATCH] Adding tests for Url namespace and starting to sort tests --- phpunit.xml.dist | 2 +- src/Guzzle/Http/Client.php | 15 +- src/Guzzle/Url/QueryString.php | 25 +- src/Guzzle/Url/UriTemplate.php | 12 - src/Guzzle/Url/Url.php | 6 +- tests/Guzzle/Tests/GuzzleTestCase.php | 199 -------------- .../Tests/Url/DuplicateAggregatorTest.php | 44 ++++ tests/Guzzle/Tests/Url/PhpAggregatorTest.php | 45 ++++ tests/Guzzle/Tests/Url/QueryStringTest.php | 176 +++++++++++++ tests/Guzzle/Tests/Url/UriTemplateTest.php | 203 +++++++++++++++ tests/Guzzle/Tests/Url/UrlTest.php | 244 ++++++++++++++++++ tests/bootstrap.php | 23 -- 12 files changed, 747 insertions(+), 247 deletions(-) create mode 100644 tests/Guzzle/Tests/Url/DuplicateAggregatorTest.php create mode 100644 tests/Guzzle/Tests/Url/PhpAggregatorTest.php create mode 100644 tests/Guzzle/Tests/Url/QueryStringTest.php create mode 100644 tests/Guzzle/Tests/Url/UriTemplateTest.php create mode 100644 tests/Guzzle/Tests/Url/UrlTest.php delete mode 100644 tests/bootstrap.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 208fdc08..fd13b91a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,5 +1,5 @@ -expand($template, $variables); + if (function_exists('uri_template')) { + return uri_template($template, $variables); + } + + if (!self::$uriTemplate) { + self::$uriTemplate = new UriTemplate(); + } + + return self::$uriTemplate->expand($template, $variables); } /** diff --git a/src/Guzzle/Url/QueryString.php b/src/Guzzle/Url/QueryString.php index 6d31b16f..5619503c 100644 --- a/src/Guzzle/Url/QueryString.php +++ b/src/Guzzle/Url/QueryString.php @@ -80,18 +80,26 @@ class QueryString extends Collection } $result = ''; - $query = $this->aggregator->aggregate($this->data); - foreach ($query as $key => $values) { + foreach ($this->aggregator->aggregate($this->data) as $key => $values) { foreach ($values as $value) { if ($result) { $result .= '&'; } if ($this->encoding == self::RFC1738) { - $result .= urlencode($key) . '=' . urlencode($value); + $result .= urlencode($key); + if ($value !== null) { + $result .= '=' . urlencode($value); + } } elseif ($this->encoding == self::RFC3986) { - $result .= rawurlencode($key) . '=' . rawurldecode($value); + $result .= rawurlencode($key); + if ($value !== null) { + $result .= '=' . rawurlencode($value); + } } else { - $result .= $key . '=' . $value; + $result .= $key; + if ($value !== null) { + $result .= '=' . $value; + } } } } @@ -119,10 +127,15 @@ class QueryString extends Collection * @param string|bool $type One of 'RFC1738', 'RFC3986', or false to disable encoding * * @return self + * @throws \InvalidArgumentException */ public function setEncodingType($type) { - $this->encoding = $type; + if ($type === false || $type === self::RFC1738 || $type === self::RFC3986) { + $this->encoding = $type; + } else { + throw new \InvalidArgumentException('Invalid URL encoding type'); + } return $this; } diff --git a/src/Guzzle/Url/UriTemplate.php b/src/Guzzle/Url/UriTemplate.php index ab627cc5..e828d9bc 100644 --- a/src/Guzzle/Url/UriTemplate.php +++ b/src/Guzzle/Url/UriTemplate.php @@ -11,9 +11,6 @@ class UriTemplate { const DEFAULT_PATTERN = '/\{([^\}]+)\}/'; - /** @var self */ - private static $instance; - /** @var string URI template */ private $template; @@ -39,15 +36,6 @@ class UriTemplate '%3B', '%3D' ); - public static function getInstance() - { - if (!self::$instance) { - self::$instance = new self(); - } - - return self::$instance; - } - public function expand($template, array $variables) { if ($this->regex == self::DEFAULT_PATTERN && false === strpos($template, '{')) { diff --git a/src/Guzzle/Url/Url.php b/src/Guzzle/Url/Url.php index 7a730128..f4aea3ba 100644 --- a/src/Guzzle/Url/Url.php +++ b/src/Guzzle/Url/Url.php @@ -93,7 +93,7 @@ class Url } // Add the query string if present - if (isset($parts['query'])) { + if (isset($parts['query']) && strlen($parts['query'])) { $url .= '?' . $parts['query']; } @@ -173,7 +173,7 @@ class Url 'host' => $this->host, 'port' => $this->port, 'path' => $this->getPath(), - 'query' => (string) $this->query ?: null, + 'query' => (string) $this->query, 'fragment' => $this->fragment, ); } @@ -294,7 +294,7 @@ class Url return $this; } - $results = array(); + $results = []; $segments = $this->getPathSegments(); foreach ($segments as $segment) { if ($segment == '..') { diff --git a/tests/Guzzle/Tests/GuzzleTestCase.php b/tests/Guzzle/Tests/GuzzleTestCase.php index 02d195c8..a844f4f6 100644 --- a/tests/Guzzle/Tests/GuzzleTestCase.php +++ b/tests/Guzzle/Tests/GuzzleTestCase.php @@ -2,32 +2,13 @@ namespace Guzzle\Tests; -use Guzzle\Common\HasDispatcherInterface; -use Guzzle\Common\Event; use Guzzle\Http\Message\Response; -use Guzzle\Http\Message\RequestInterface; -use Guzzle\Tests\Http\Message\HeaderComparison; -use Guzzle\Plugin\Mock\MockPlugin; -use Guzzle\Service\Client; -use Guzzle\Service\Builder\ServiceBuilderInterface; -use Guzzle\Service\Builder\ServiceBuilder; -use Guzzle\Tests\Mock\MockObserver; use Guzzle\Tests\Http\Server; -use RuntimeException; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -/** - * Base testcase class for all Guzzle testcases. - */ abstract class GuzzleTestCase extends \PHPUnit_Framework_TestCase { - protected static $mockBasePath; - public static $serviceBuilder; public static $server; - private $requests = array(); - public $mockObserver; - /** * Get the global server object used throughout the unit tests of Guzzle * @@ -46,184 +27,4 @@ abstract class GuzzleTestCase extends \PHPUnit_Framework_TestCase return self::$server; } - - /** - * Set the service builder to use for tests - * - * @param ServiceBuilderInterface $builder Service builder - */ - public static function setServiceBuilder(ServiceBuilderInterface $builder) - { - self::$serviceBuilder = $builder; - } - - /** - * Get a service builder object that can be used throughout the service tests - * - * @return ServiceBuilder - */ - public static function getServiceBuilder() - { - if (!self::$serviceBuilder) { - throw new RuntimeException('No service builder has been set via setServiceBuilder()'); - } - - return self::$serviceBuilder; - } - - /** - * Check if an event dispatcher has a subscriber - * - * @param HasDispatcherInterface $dispatcher - * @param EventSubscriberInterface $subscriber - * - * @return bool - */ - protected function hasSubscriber(HasDispatcherInterface $dispatcher, EventSubscriberInterface $subscriber) - { - $class = get_class($subscriber); - $all = array_keys(call_user_func(array($class, 'getSubscribedEvents'))); - - foreach ($all as $i => $event) { - foreach ($dispatcher->getEventDispatcher()->getListeners($event) as $e) { - if ($e[0] === $subscriber) { - unset($all[$i]); - break; - } - } - } - - return count($all) == 0; - } - - /** - * Get a wildcard observer for an event dispatcher - * - * @param HasDispatcherInterface $hasEvent - * - * @return MockObserver - */ - public function getWildcardObserver(HasDispatcherInterface $hasDispatcher) - { - $class = get_class($hasDispatcher); - $o = new MockObserver(); - $events = call_user_func(array($class, 'getAllEvents')); - foreach ($events as $event) { - $hasDispatcher->getEventDispatcher()->addListener($event, array($o, 'update')); - } - - return $o; - } - - /** - * Set the mock response base path - * - * @param string $path Path to mock response folder - * - * @return GuzzleTestCase - */ - public static function setMockBasePath($path) - { - self::$mockBasePath = $path; - } - - /** - * Mark a request as being mocked - * - * @param RequestInterface $request - * - * @return self - */ - public function addMockedRequest(RequestInterface $request) - { - $this->requests[] = $request; - - return $this; - } - - /** - * Get all of the mocked requests - * - * @return array - */ - public function getMockedRequests() - { - return $this->requests; - } - - /** - * Get a mock response for a client by mock file name - * - * @param string $path Relative path to the mock response file - * - * @return Response - */ - public function getMockResponse($path) - { - return $path instanceof Response - ? $path - : MockPlugin::getMockFile(self::$mockBasePath . DIRECTORY_SEPARATOR . $path); - } - - /** - * Set a mock response from a mock file on the next client request. - * - * This method assumes that mock response files are located under the - * Command/Mock/ directory of the Service being tested - * (e.g. Unfuddle/Command/Mock/). A mock response is added to the next - * request sent by the client. - * - * @param Client $client Client object to modify - * @param string $paths Path to files within the Mock folder of the service - * - * @return MockPlugin returns the created mock plugin - */ - public function setMockResponse(Client $client, $paths) - { - $this->requests = array(); - $that = $this; - $mock = new MockPlugin(null, true); - $client->getEventDispatcher()->removeSubscriber($mock); - $mock->getEventDispatcher()->addListener('mock.request', function(Event $event) use ($that) { - $that->addMockedRequest($event['request']); - }); - - foreach ((array) $paths as $path) { - $mock->addResponse($this->getMockResponse($path)); - } - - $client->getEventDispatcher()->addSubscriber($mock); - - return $mock; - } - - /** - * Compare HTTP headers and use special markup to filter values - * A header prefixed with '!' means it must not exist - * A header prefixed with '_' means it must be ignored - * A header value of '*' means anything after the * will be ignored - * - * @param array $filteredHeaders Array of special headers - * @param array $actualHeaders Array of headers to check against - * - * @return array|bool Returns an array of the differences or FALSE if none - */ - public function compareHeaders($filteredHeaders, $actualHeaders) - { - $comparison = new HeaderComparison(); - - return $comparison->compare($filteredHeaders, $actualHeaders); - } - - /** - * Case insensitive assertContains - * - * @param string $needle Search string - * @param string $haystack Search this - * @param string $message Optional failure message - */ - public function assertContainsIns($needle, $haystack, $message = null) - { - $this->assertContains(strtolower($needle), strtolower($haystack), $message); - } } diff --git a/tests/Guzzle/Tests/Url/DuplicateAggregatorTest.php b/tests/Guzzle/Tests/Url/DuplicateAggregatorTest.php new file mode 100644 index 00000000..51b54f43 --- /dev/null +++ b/tests/Guzzle/Tests/Url/DuplicateAggregatorTest.php @@ -0,0 +1,44 @@ + [ + 'v1' => ['a', '1'], + 'v2' => 'b', + 'v3' => ['v4' => 'c', 'v5' => 'd'] + ] + ]; + + public function testEncodes() + { + $agg = new DuplicateAggregator(); + $result = $agg->aggregate($this->encodeData); + $this->assertEquals(array( + 't[v1]' => ['a', '1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } + + public function testEncodesNoNumericIndices() + { + $agg = new DuplicateAggregator(false); + $result = $agg->aggregate($this->encodeData); + $this->assertEquals(array( + 't[v1]' => ['a', '1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } +} diff --git a/tests/Guzzle/Tests/Url/PhpAggregatorTest.php b/tests/Guzzle/Tests/Url/PhpAggregatorTest.php new file mode 100644 index 00000000..da8257d5 --- /dev/null +++ b/tests/Guzzle/Tests/Url/PhpAggregatorTest.php @@ -0,0 +1,45 @@ + [ + 'v1' => ['a', '1'], + 'v2' => 'b', + 'v3' => ['v4' => 'c', 'v5' => 'd'] + ] + ]; + + public function testEncodes() + { + $agg = new PhpAggregator(); + $result = $agg->aggregate($this->encodeData); + $this->assertEquals(array( + 't[v1][0]' => ['a'], + 't[v1][1]' => ['1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } + + public function testEncodesNoNumericIndices() + { + $agg = new PhpAggregator(false); + $result = $agg->aggregate($this->encodeData); + $this->assertEquals(array( + 't[v1][]' => ['a', '1'], + 't[v2]' => ['b'], + 't[v3][v4]' => ['c'], + 't[v3][v5]' => ['d'], + ), $result); + } +} diff --git a/tests/Guzzle/Tests/Url/QueryStringTest.php b/tests/Guzzle/Tests/Url/QueryStringTest.php new file mode 100644 index 00000000..c5a05b35 --- /dev/null +++ b/tests/Guzzle/Tests/Url/QueryStringTest.php @@ -0,0 +1,176 @@ + 'baz', 'bar' => 'bam boozle']); + $this->assertEquals('foo=baz&bar=bam%20boozle', (string) $q); + } + + public function testCanDisableUrlEncoding() + { + $q = new QueryString(['bar' => 'bam boozle']); + $q->setEncodingType(false); + $this->assertEquals('bar=bam boozle', (string) $q); + } + + public function testCanSpecifyRfc1783UrlEncodingType() + { + $q = new QueryString(['bar abc' => 'bam boozle']); + $q->setEncodingType(QueryString::RFC1738); + $this->assertEquals('bar+abc=bam+boozle', (string) $q); + } + + public function testCanSpecifyRfc3986UrlEncodingType() + { + $q = new QueryString(['bar abc' => 'bam boozle', 'ሴ' => 'hi']); + $q->setEncodingType(QueryString::RFC3986); + $this->assertEquals('bar%20abc=bam%20boozle&%E1%88%B4=hi', (string) $q); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesEncodingType() + { + (new QueryString(['bar' => 'bam boozle']))->setEncodingType('foo'); + } + + public function testAggregatesMultipleValues() + { + $q = new QueryString(['foo' => ['bar', 'baz']]); + $this->assertEquals('foo%5B0%5D=bar&foo%5B1%5D=baz', (string) $q); + } + + public function testCanSetAggregator() + { + $agg = $this->getMockBuilder('Guzzle\Url\QueryAggregatorInterface') + ->setMethods('aggregate') + ->getMockForAbstractClass(); + + $q = new QueryString(['foo' => ['bar', 'baz']]); + $q->setAggregator($agg); + + $agg->expects($this->once()) + ->method('aggregate') + ->will($this->returnValue(['foo' => ['barANDbaz']])); + + $this->assertEquals('foo=barANDbaz', (string) $q); + } + + public function testAllowsMultipleValuesPerKey() + { + $q = new QueryString(); + $q->add('facet', 'size'); + $q->add('facet', 'width'); + $q->add('facet.field', 'foo'); + // Use the duplicate aggregator + $q->setAggregator(new DuplicateAggregator()); + $this->assertEquals('facet=size&facet=width&facet.field=foo', (string) $q); + } + + public function parseQueryProvider() + { + return array( + // Ensure that multiple query string values are allowed per value + array('q=a&q=b', array('q' => array('a', 'b'))), + // Ensure that PHP array style query string values are parsed + array('q[]=a&q[]=b', array('q' => array('a', 'b'))), + // Ensure that a single PHP array style query string value is parsed into an array + array('q[]=a', array('q' => array('a'))), + // Ensure that decimals are allowed in query strings + array('q.a=a&q.b=b', array( + 'q.a' => 'a', + 'q.b' => 'b' + )), + // Ensure that query string values are percent decoded + array('q%20a=a%20b', array('q a' => 'a b')), + // Ensure null values can be added + array('q&a', array('q' => null, 'a' => null)), + ); + } + + /** + * @dataProvider parseQueryProvider + */ + public function testParsesQueryStrings($query, $data) + { + $query = QueryString::fromString($query); + $this->assertEquals($data, $query->toArray()); + } + + public function testProperlyDealsWithDuplicateQueryStringValues() + { + $query = QueryString::fromString('foo=a&foo=b&?µ=c'); + $this->assertEquals(array('a', 'b'), $query->get('foo')); + $this->assertEquals('c', $query->get('?µ')); + } + + public function testAllowsNullQueryStringValues() + { + $query = QueryString::fromString('foo'); + $this->assertEquals('foo', (string) $query); + $query->set('foo', null); + $this->assertEquals('foo', (string) $query); + } + + public function testAllowsFalsyQueryStringValues() + { + $query = QueryString::fromString('0'); + $this->assertEquals('0', (string) $query); + $query->set('0', ''); + $this->assertSame('0=', (string) $query); + } + + public function testConvertsPlusSymbolsToSpaces() + { + $query = QueryString::fromString('var=foo+bar'); + $this->assertEquals('foo bar', $query->get('var')); + } + + public function testFromStringDoesntMangleZeroes() + { + $query = QueryString::fromString('var=0'); + $this->assertSame('0', $query->get('var')); + } + + public function testAllowsZeroValues() + { + $query = new QueryString(array( + 'foo' => 0, + 'baz' => '0', + 'bar' => null, + 'boo' => false + )); + $this->assertEquals('foo=0&baz=0&bar&boo=', (string) $query); + } + + public function testFromStringDoesntStripTrailingEquals() + { + $query = QueryString::fromString('data=mF0b3IiLCJUZWFtIERldiJdfX0='); + $this->assertEquals('mF0b3IiLCJUZWFtIERldiJdfX0=', $query->get('data')); + } + + public function testGuessesIfDuplicateAggregatorShouldBeUsed() + { + $query = QueryString::fromString('test=a&test=b'); + $this->assertEquals('test=a&test=b', (string) $query); + } + + public function testGuessesIfDuplicateAggregatorShouldBeUsedAndChecksForPhpStyle() + { + $query = QueryString::fromString('test[]=a&test[]=b'); + $this->assertEquals('test%5B0%5D=a&test%5B1%5D=b', (string) $query); + } + + public function testCastingToAndCreatingFromStringWithEmptyValuesIsFast() + { + $this->assertEquals('', (string) QueryString::fromString('')); + } +} diff --git a/tests/Guzzle/Tests/Url/UriTemplateTest.php b/tests/Guzzle/Tests/Url/UriTemplateTest.php new file mode 100644 index 00000000..8c4371aa --- /dev/null +++ b/tests/Guzzle/Tests/Url/UriTemplateTest.php @@ -0,0 +1,203 @@ + 'value', + 'hello' => 'Hello World!', + 'empty' => '', + 'path' => '/foo/bar', + 'x' => '1024', + 'y' => '768', + 'null' => null, + 'list' => array('red', 'green', 'blue'), + 'keys' => array( + "semi" => ';', + "dot" => '.', + "comma" => ',' + ), + 'empty_keys' => array(), + ); + + return array_map(function($t) use ($params) { + $t[] = $params; + return $t; + }, array( + array('foo', 'foo'), + array('{var}', 'value'), + array('{hello}', 'Hello%20World%21'), + array('{+var}', 'value'), + array('{+hello}', 'Hello%20World!'), + array('{+path}/here', '/foo/bar/here'), + array('here?ref={+path}', 'here?ref=/foo/bar'), + array('X{#var}', 'X#value'), + array('X{#hello}', 'X#Hello%20World!'), + array('map?{x,y}', 'map?1024,768'), + array('{x,hello,y}', '1024,Hello%20World%21,768'), + array('{+x,hello,y}', '1024,Hello%20World!,768'), + array('{+path,x}/here', '/foo/bar,1024/here'), + array('{#x,hello,y}', '#1024,Hello%20World!,768'), + array('{#path,x}/here', '#/foo/bar,1024/here'), + array('X{.var}', 'X.value'), + array('X{.x,y}', 'X.1024.768'), + array('{/var}', '/value'), + array('{/var,x}/here', '/value/1024/here'), + array('{;x,y}', ';x=1024;y=768'), + array('{;x,y,empty}', ';x=1024;y=768;empty'), + array('{?x,y}', '?x=1024&y=768'), + array('{?x,y,empty}', '?x=1024&y=768&empty='), + array('?fixed=yes{&x}', '?fixed=yes&x=1024'), + array('{&x,y,empty}', '&x=1024&y=768&empty='), + array('{var:3}', 'val'), + array('{var:30}', 'value'), + array('{list}', 'red,green,blue'), + array('{list*}', 'red,green,blue'), + array('{keys}', 'semi,%3B,dot,.,comma,%2C'), + array('{keys*}', 'semi=%3B,dot=.,comma=%2C'), + array('{+path:6}/here', '/foo/b/here'), + array('{+list}', 'red,green,blue'), + array('{+list*}', 'red,green,blue'), + array('{+keys}', 'semi,;,dot,.,comma,,'), + array('{+keys*}', 'semi=;,dot=.,comma=,'), + array('{#path:6}/here', '#/foo/b/here'), + array('{#list}', '#red,green,blue'), + array('{#list*}', '#red,green,blue'), + array('{#keys}', '#semi,;,dot,.,comma,,'), + array('{#keys*}', '#semi=;,dot=.,comma=,'), + array('X{.var:3}', 'X.val'), + array('X{.list}', 'X.red,green,blue'), + array('X{.list*}', 'X.red.green.blue'), + array('X{.keys}', 'X.semi,%3B,dot,.,comma,%2C'), + array('X{.keys*}', 'X.semi=%3B.dot=..comma=%2C'), + array('{/var:1,var}', '/v/value'), + array('{/list}', '/red,green,blue'), + array('{/list*}', '/red/green/blue'), + array('{/list*,path:4}', '/red/green/blue/%2Ffoo'), + array('{/keys}', '/semi,%3B,dot,.,comma,%2C'), + array('{/keys*}', '/semi=%3B/dot=./comma=%2C'), + array('{;hello:5}', ';hello=Hello'), + array('{;list}', ';list=red,green,blue'), + array('{;list*}', ';list=red;list=green;list=blue'), + array('{;keys}', ';keys=semi,%3B,dot,.,comma,%2C'), + array('{;keys*}', ';semi=%3B;dot=.;comma=%2C'), + array('{?var:3}', '?var=val'), + array('{?list}', '?list=red,green,blue'), + array('{?list*}', '?list=red&list=green&list=blue'), + array('{?keys}', '?keys=semi,%3B,dot,.,comma,%2C'), + array('{?keys*}', '?semi=%3B&dot=.&comma=%2C'), + array('{&var:3}', '&var=val'), + array('{&list}', '&list=red,green,blue'), + array('{&list*}', '&list=red&list=green&list=blue'), + array('{&keys}', '&keys=semi,%3B,dot,.,comma,%2C'), + array('{&keys*}', '&semi=%3B&dot=.&comma=%2C'), + array('{.null}', ''), + array('{.null,var}', '.value'), + array('X{.empty_keys*}', 'X'), + array('X{.empty_keys}', 'X'), + // Test that missing expansions are skipped + array('test{&missing*}', 'test'), + // Test that multiple expansions can be set + array('http://{var}/{var:2}{?keys*}', 'http://value/va?semi=%3B&dot=.&comma=%2C'), + // Test more complex query string stuff + array('http://www.test.com{+path}{?var,keys*}', 'http://www.test.com/foo/bar?var=value&semi=%3B&dot=.&comma=%2C') + )); + } + + /** + * @dataProvider templateProvider + */ + public function testExpandsUriTemplates($template, $expansion, $params) + { + $uri = new UriTemplate($template); + $this->assertEquals($expansion, $uri->expand($template, $params)); + } + + public function expressionProvider() + { + return array( + array( + '{+var*}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'var', 'modifier' => '*') + ) + ), + ), + array( + '{?keys,var,val}', array( + 'operator' => '?', + 'values' => array( + array('value' => 'keys', 'modifier' => ''), + array('value' => 'var', 'modifier' => ''), + array('value' => 'val', 'modifier' => '') + ) + ), + ), + array( + '{+x,hello,y}', array( + 'operator' => '+', + 'values' => array( + array('value' => 'x', 'modifier' => ''), + array('value' => 'hello', 'modifier' => ''), + array('value' => 'y', 'modifier' => '') + ) + ) + ) + ); + } + + /** + * @dataProvider expressionProvider + */ + public function testParsesExpressions($exp, $data) + { + $template = new UriTemplate($exp); + + // Access the config object + $class = new \ReflectionClass($template); + $method = $class->getMethod('parseExpression'); + $method->setAccessible(true); + + $exp = substr($exp, 1, -1); + $this->assertEquals($data, $method->invokeArgs($template, array($exp))); + } + + /** + * @ticket https://github.com/guzzle/guzzle/issues/90 + */ + public function testAllowsNestedArrayExpansion() + { + $template = new UriTemplate(); + + $result = $template->expand('http://example.com{+path}{/segments}{?query,data*,foo*}', array( + 'path' => '/foo/bar', + 'segments' => array('one', 'two'), + 'query' => 'test', + 'data' => array( + 'more' => array('fun', 'ice cream') + ), + 'foo' => array( + 'baz' => array( + 'bar' => 'fizz', + 'test' => 'buzz' + ), + 'bam' => 'boo' + ) + )); + + $this->assertEquals('http://example.com/foo/bar/one,two?query=test&more%5B0%5D=fun&more%5B1%5D=ice%20cream&baz%5Bbar%5D=fizz&baz%5Btest%5D=buzz&bam=boo', $result); + } +} diff --git a/tests/Guzzle/Tests/Url/UrlTest.php b/tests/Guzzle/Tests/Url/UrlTest.php new file mode 100644 index 00000000..cab2f3f1 --- /dev/null +++ b/tests/Guzzle/Tests/Url/UrlTest.php @@ -0,0 +1,244 @@ +assertEquals('', (string) $url); + } + + public function testPortIsDeterminedFromScheme() + { + $this->assertEquals(80, Url::fromString('http://www.test.com/')->getPort()); + $this->assertEquals(443, Url::fromString('https://www.test.com/')->getPort()); + $this->assertEquals(null, Url::fromString('ftp://www.test.com/')->getPort()); + $this->assertEquals(8192, Url::fromString('http://www.test.com:8192/')->getPort()); + } + + public function testCloneCreatesNewInternalObjects() + { + $u1 = Url::fromString('http://www.test.com/'); + $u2 = clone $u1; + $this->assertNotSame($u1->getQuery(), $u2->getQuery()); + } + + public function testValidatesUrlPartsInFactory() + { + $url = Url::fromString('/index.php'); + $this->assertEquals('/index.php', (string) $url); + $this->assertFalse($url->isAbsolute()); + + $url = 'http://michael:test@test.com:80/path/123?q=abc#test'; + $u = Url::fromString($url); + $this->assertEquals('http://michael:test@test.com/path/123?q=abc#test', (string) $u); + $this->assertTrue($u->isAbsolute()); + } + + public function testAllowsFalsyUrlParts() + { + $url = Url::fromString('http://0:50/0?0#0'); + $this->assertSame('0', $url->getHost()); + $this->assertEquals(50, $url->getPort()); + $this->assertSame('/0', $url->getPath()); + $this->assertEquals('0', (string) $url->getQuery()); + $this->assertSame('0', $url->getFragment()); + $this->assertEquals('http://0:50/0?0#0', (string) $url); + + $url = Url::fromString(''); + $this->assertSame('', (string) $url); + + $url = Url::fromString('0'); + $this->assertSame('0', (string) $url); + } + + public function testBuildsRelativeUrlsWithFalsyParts() + { + $url = Url::buildUrl(array( + 'host' => '0', + 'path' => '0', + )); + + $this->assertSame('//0/0', $url); + + $url = Url::buildUrl(array( + 'path' => '0', + )); + $this->assertSame('0', $url); + } + + public function testUrlStoresParts() + { + $url = Url::fromString('http://test:pass@www.test.com:8081/path/path2/?a=1&b=2#fragment'); + $this->assertEquals('http', $url->getScheme()); + $this->assertEquals('test', $url->getUsername()); + $this->assertEquals('pass', $url->getPassword()); + $this->assertEquals('www.test.com', $url->getHost()); + $this->assertEquals(8081, $url->getPort()); + $this->assertEquals('/path/path2/', $url->getPath()); + $this->assertEquals('fragment', $url->getFragment()); + $this->assertEquals('a=1&b=2', (string) $url->getQuery()); + + $this->assertEquals(array( + 'fragment' => 'fragment', + 'host' => 'www.test.com', + 'pass' => 'pass', + 'path' => '/path/path2/', + 'port' => 8081, + 'query' => 'a=1&b=2', + 'scheme' => 'http', + 'user' => 'test' + ), $url->getParts()); + } + + public function testHandlesPathsCorrectly() + { + $url = Url::fromString('http://www.test.com'); + $this->assertEquals('', $url->getPath()); + $url->setPath('test'); + $this->assertEquals('test', $url->getPath()); + + $url->setPath('/test/123/abc'); + $this->assertEquals(array('test', '123', 'abc'), $url->getPathSegments()); + + $parts = parse_url('http://www.test.com/test'); + $parts['path'] = ''; + $this->assertEquals('http://www.test.com', Url::buildUrl($parts)); + $parts['path'] = 'test'; + $this->assertEquals('http://www.test.com/test', Url::buildUrl($parts)); + } + + public function testAddsQueryStringIfPresent() + { + $this->assertEquals('?foo=bar', Url::buildUrl(array( + 'query' => 'foo=bar' + ))); + } + + public function testAddsToPath() + { + // Does nothing here + $this->assertEquals('http://e.com/base?a=1', (string) Url::fromString('http://e.com/base?a=1')->addPath(false)); + $this->assertEquals('http://e.com/base?a=1', (string) Url::fromString('http://e.com/base?a=1')->addPath('')); + $this->assertEquals('http://e.com/base?a=1', (string) Url::fromString('http://e.com/base?a=1')->addPath('/')); + + $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::fromString('http://e.com/base?a=1')->addPath('relative')); + $this->assertEquals('http://e.com/base/relative?a=1', (string) Url::fromString('http://e.com/base?a=1')->addPath('/relative')); + } + + /** + * URL combination data provider + * + * @return array + */ + public function urlCombineDataProvider() + { + return array( + array('http://www.example.com/', 'http://www.example.com/', 'http://www.example.com/'), + array('http://www.example.com/path', '/absolute', 'http://www.example.com/absolute'), + array('http://www.example.com/path', '/absolute?q=2', 'http://www.example.com/absolute?q=2'), + array('http://www.example.com/path', 'more', 'http://www.example.com/path/more'), + array('http://www.example.com/path', 'more?q=1', 'http://www.example.com/path/more?q=1'), + array('http://www.example.com/', '?q=1', 'http://www.example.com/?q=1'), + array('http://www.example.com/path', 'http://test.com', 'http://test.com'), + array('http://www.example.com:8080/path', 'http://test.com', 'http://test.com'), + array('http://www.example.com:8080/path', '?q=2#abc', 'http://www.example.com:8080/path?q=2#abc'), + array('http://u:a@www.example.com/path', 'test', 'http://u:a@www.example.com/path/test'), + array('http://www.example.com/path', 'http://u:a@www.example.com/', 'http://u:a@www.example.com/'), + array('/path?q=2', 'http://www.test.com/', 'http://www.test.com/path?q=2'), + array('http://api.flickr.com/services/', 'http://www.flickr.com/services/oauth/access_token', 'http://www.flickr.com/services/oauth/access_token'), + array('https://www.example.com/path', '//foo.com/abc', 'https://foo.com/abc'), + ); + } + + /** + * @dataProvider urlCombineDataProvider + */ + public function testCombinesUrls($a, $b, $c) + { + $this->assertEquals($c, (string) Url::fromString($a)->combine($b)); + } + + public function testHasGettersAndSetters() + { + $url = Url::fromString('http://www.test.com/'); + $this->assertEquals('example.com', $url->setHost('example.com')->getHost()); + $this->assertEquals('8080', $url->setPort(8080)->getPort()); + $this->assertEquals('/foo/bar', $url->setPath(array('foo', 'bar'))->getPath()); + $this->assertEquals('a', $url->setPassword('a')->getPassword()); + $this->assertEquals('b', $url->setUsername('b')->getUsername()); + $this->assertEquals('abc', $url->setFragment('abc')->getFragment()); + $this->assertEquals('https', $url->setScheme('https')->getScheme()); + $this->assertEquals('a=123', (string) $url->setQuery('a=123')->getQuery()); + $this->assertEquals('https://b:a@example.com:8080/foo/bar?a=123#abc', (string) $url); + $this->assertEquals('b=boo', (string) $url->setQuery(new QueryString(array( + 'b' => 'boo' + )))->getQuery()); + $this->assertEquals('https://b:a@example.com:8080/foo/bar?b=boo#abc', (string) $url); + } + + public function testSetQueryAcceptsArray() + { + $url = Url::fromString('http://www.test.com'); + $url->setQuery(array('a' => 'b')); + $this->assertEquals('http://www.test.com?a=b', (string) $url); + } + + public function urlProvider() + { + return array( + array('/foo/..', '/'), + array('//foo//..', '/'), + array('/foo/../..', '/'), + array('/foo/../.', '/'), + array('/./foo/..', '/'), + array('/./foo', '/foo'), + array('/./foo/', '/foo/'), + array('/./foo/bar/baz/pho/../..', '/foo/bar'), + array('*', '*'), + array('/foo', '/foo'), + array('/abc/123/../foo/', '/abc/foo/') + ); + } + + /** + * @dataProvider urlProvider + */ + public function testNormalizesPaths($path, $result) + { + $url = Url::fromString('http://www.example.com'); + $url->setPath($path)->normalizePath(); + $this->assertEquals($result, $url->getPath()); + } + + public function testSettingHostWithPortModifiesPort() + { + $url = Url::fromString('http://www.example.com'); + $url->setHost('foo:8983'); + $this->assertEquals('foo', $url->getHost()); + $this->assertEquals(8983, $url->getPort()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testValidatesUrlCanBeParsed() + { + Url::fromString('foo:////'); + } + + public function testConvertsSpecialCharsInPathWhenCastingToString() + { + $url = Url::fromString('http://foo.com/baz bar?a=b'); + $url->addPath('?'); + $this->assertEquals('http://foo.com/baz%20bar/%3F?a=b', (string) $url); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index e125001a..00000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,23 +0,0 @@ -