diff --git a/server/vendor/php-opencloud/common/.gitignore b/server/vendor/php-opencloud/common/.gitignore new file mode 100644 index 0000000..7ad09a8 --- /dev/null +++ b/server/vendor/php-opencloud/common/.gitignore @@ -0,0 +1,10 @@ +.idea/ +.test/ +coverage/ +vendor/ + +*.pyc + +phpunit.xml +coverage.xml +composer.lock diff --git a/server/vendor/php-opencloud/common/composer.json b/server/vendor/php-opencloud/common/composer.json new file mode 100644 index 0000000..f2520ad --- /dev/null +++ b/server/vendor/php-opencloud/common/composer.json @@ -0,0 +1,36 @@ +{ + "name": "php-opencloud/common", + "authors": [ + { + "name": "Jamie Hannaford", + "email": "jamie.hannaford@rackspace.com", + "homepage" : "https://github.com/jamiehannaford" + } + ], + "autoload": { + "psr-4": { + "OpenCloud\\": "src/", + "OpenCloud\\Test\\": "tests/unit/", + "OpenCloud\\Integration\\": "tests/integration/" + } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/php-opencloud/Sami" + } + ], + "require": { + "php": "~7.0", + "guzzlehttp/guzzle": "~6.1", + "justinrainbow/json-schema": "~1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "sami/sami": "dev-master", + "psr/log": "~1.0", + "satooshi/php-coveralls": "~1.0", + "jakub-onderka/php-parallel-lint": "0.*", + "fabpot/php-cs-fixer": "~1.0" + } +} diff --git a/server/vendor/php-opencloud/common/phpunit.xml.dist b/server/vendor/php-opencloud/common/phpunit.xml.dist new file mode 100644 index 0000000..cf698d5 --- /dev/null +++ b/server/vendor/php-opencloud/common/phpunit.xml.dist @@ -0,0 +1,21 @@ + + + + + + tests/unit + + + + + + ./src + + ./src + ./src + ./src + + + + + \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php b/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php new file mode 100644 index 0000000..beee5e8 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php @@ -0,0 +1,33 @@ + true]); + } + + protected function notRequired(array $param) + { + return array_merge($param, ['required' => false]); + } + + protected function query(array $param) + { + return array_merge($param, ['location' => AbstractParams::QUERY]); + } + + protected function url(array $param) + { + return array_merge($param, ['location' => AbstractParams::URL]); + } + + public function documented(array $param) + { + return array_merge($param, ['required' => true]); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php b/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php new file mode 100644 index 0000000..225e025 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php @@ -0,0 +1,100 @@ + self::INT_TYPE, + 'location' => 'query', + 'description' => << 'string', + 'location' => 'query', + 'description' => << sprintf("The unique ID, or identifier, for the %s", $type), + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + ]; + } + + public function idPath() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::URL, + 'description' => 'The unique ID of the resource', + ]; + } + + public function name($resource) + { + return [ + 'description' => sprintf("The name of the %s", $resource), + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + ]; + } + + + public function sortDir() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::QUERY, + 'description' => "Sorts by one or more sets of attribute and sort direction combinations.", + 'enum' => ['asc', 'desc'] + ]; + } + + public function sortKey() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::QUERY, + 'description' => "Sorts by one or more sets of attribute and sort direction combinations.", + ]; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php b/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php new file mode 100644 index 0000000..d5d26a0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php @@ -0,0 +1,20 @@ +method = $definition['method']; + $this->path = $definition['path']; + + if (isset($definition['jsonKey'])) { + $this->jsonKey = $definition['jsonKey']; + } + + $this->params = self::toParamArray($definition['params']); + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Indicates whether this operation supports a parameter. + * + * @param $key The name of a parameter + * + * @return bool + */ + public function hasParam($key) + { + return isset($this->params[$key]); + } + + /** + * @param $name + * + * @return Parameter + */ + public function getParam($name) + { + return isset($this->params[$name]) ? $this->params[$name] : null; + } + + /** + * @return string + */ + public function getJsonKey() + { + return $this->jsonKey; + } + + /** + * A convenience method that will take a generic array of data and convert it into an array of + * {@see Parameter} objects. + * + * @param array $data A generic data array + * + * @return array + */ + public static function toParamArray(array $data) + { + $params = []; + + foreach ($data as $name => $param) { + $params[$name] = new Parameter($param + ['name' => $name]); + } + + return $params; + } + + /** + * This method will validate all of the user-provided values and throw an exception if any + * failures are detected. This is useful for basic sanity-checking before a request is + * serialized and sent to the API. + * + * @param array $userValues The user-defined values + * + * @return bool TRUE if validation passes + * @throws \Exception If validate fails + */ + public function validate(array $userValues) + { + foreach ($this->params as $paramName => $param) { + if (array_key_exists($paramName, $userValues)) { + $param->validate($userValues[$paramName]); + } elseif ($param->isRequired()) { + throw new \Exception(sprintf('"%s" is a required option, but it was not provided', $paramName)); + } + } + + return true; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Operator.php b/server/vendor/php-opencloud/common/src/Common/Api/Operator.php new file mode 100644 index 0000000..4325b69 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Operator.php @@ -0,0 +1,173 @@ +client = $client; + $this->api = $api; + } + + /** + * Magic method for dictating how objects are rendered when var_dump is called. + * For the benefit of users, extremely verbose and heavy properties (such as HTTP clients) are + * removed to provide easier access to normal state, such as resource attributes. + * + * @codeCoverageIgnore + * @return array + */ + public function __debugInfo() + { + $excludedVars = ['client', 'errorBuilder', 'api']; + + $output = []; + + foreach (get_object_vars($this) as $key => $val) { + if (!in_array($key, $excludedVars)) { + $output[$key] = $val; + } + } + + return $output; + } + + /** + * Retrieves a populated Operation according to the definition and values provided. A + * HTTP client is also injected into the object to allow it to communicate with the remote API. + * + * @param array $definition The data that dictates how the operation works + * + * @return Operation + */ + public function getOperation(array $definition) + { + return new Operation($definition); + } + + protected function sendRequest(Operation $operation, array $userValues = [], $async = false) + { + $operation->validate($userValues); + + $options = (new RequestSerializer)->serializeOptions($operation, $userValues); + $method = $async ? 'requestAsync' : 'request'; + $uri = uri_template($operation->getPath(), $userValues); + + return $this->client->$method($operation->getMethod(), $uri, $options); + } + + /** + * {@inheritDoc} + */ + public function execute(array $definition, array $userValues = []) + { + return $this->sendRequest($this->getOperation($definition), $userValues); + } + + /** + * {@inheritDoc} + */ + public function executeAsync(array $definition, array $userValues = []) + { + return $this->sendRequest($this->getOperation($definition), $userValues, true); + } + + /** + * {@inheritDoc} + */ + public function model($class, $data = null) + { + $model = new $class($this->client, $this->api); + + // @codeCoverageIgnoreStart + if (!$model instanceof ResourceInterface) { + throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class)); + } + // @codeCoverageIgnoreEnd + + if ($data instanceof ResponseInterface) { + $model->populateFromResponse($data); + } elseif (is_array($data)) { + $model->populateFromArray($data); + } + + return $model; + } + + /** + * Will create a new instance of this class with the current HTTP client and API injected in. This + * is useful when enumerating over a collection since multiple copies of the same resource class + * are needed. + * + * @return static + */ + public function newInstance() + { + return new static($this->client, $this->api); + } + + /** + * @return \GuzzleHttp\Psr7\Uri + */ + protected function getHttpBaseUrl() + { + return $this->client->getConfig('base_uri'); + } + + /** + * Magic method which intercepts async calls, finds the sequential version, and wraps it in a + * {@see Promise} object. In order for this to happen, the called methods need to be in the + * following format: `createAsync`, where `create` is the sequential method being wrapped. + * + * @param $methodName The name of the method being invoked. + * @param $args The arguments to be passed to the sequential method. + * + * @throws \RuntimeException If method does not exist + * + * @return Promise + */ + public function __call($methodName, $args) + { + $e = function ($name) { + return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name)); + }; + + if (substr($methodName, -5) === 'Async') { + $realMethod = substr($methodName, 0, -5); + if (!method_exists($this, $realMethod)) { + throw $e($realMethod); + } + + $promise = new Promise( + function () use (&$promise, $realMethod, $args) { + $value = call_user_func_array([$this, $realMethod], $args); + $promise->resolve($value); + } + ); + + return $promise; + } + + throw $e($methodName); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php b/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php new file mode 100644 index 0000000..43c6ce2 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php @@ -0,0 +1,51 @@ +hydrate($data); + + $this->required = (bool)$this->required; + + $this->stockLocation($data); + $this->stockItemSchema($data); + $this->stockProperties($data); + } + + private function stockLocation(array $data) + { + $this->location = isset($data['location']) ? $data['location'] : self::DEFAULT_LOCATION; + + if (!AbstractParams::isSupportedLocation($this->location)) { + throw new \RuntimeException(sprintf("%s is not a permitted location", $this->location)); + } + } + + private function stockItemSchema(array $data) + { + if (isset($data['items'])) { + $this->itemSchema = new Parameter($data['items']); + } + } + + private function stockProperties(array $data) + { + if (isset($data['properties'])) { + if (stripos($this->name, 'metadata') !== false) { + $this->properties = new Parameter($data['properties']); + } else { + foreach ($data['properties'] as $name => $property) { + $this->properties[$name] = new Parameter($property + ['name' => $name]); + } + } + } + } + + /** + * Retrieve the name that will be used over the wire. + * + * @return string + */ + public function getName() + { + return $this->sentAs ?: $this->name; + } + + /** + * Indicates whether the user must provide a value for this parameter. + * + * @return bool + */ + public function isRequired() + { + return $this->required === true; + } + + /** + * Validates a given user value and checks whether it passes basic sanity checking, such as types. + * + * @param $userValues The value provided by the user + * + * @return bool TRUE if the validation passes + * @throws \Exception If validation fails + */ + public function validate($userValues) + { + $this->validateEnums($userValues); + $this->validateType($userValues); + + if ($this->isArray()) { + $this->validateArray($userValues); + } elseif ($this->isObject()) { + $this->validateObject($userValues); + } + + return true; + } + + private function validateEnums($userValues) + { + if (!empty($this->enum) && $this->type == 'string' && !in_array($userValues, $this->enum)) { + throw new \Exception(sprintf( + 'The only permitted values are %s. You provided %s', implode(', ', $this->enum), print_r($userValues, true) + )); + } + } + + private function validateType($userValues) + { + if (!$this->hasCorrectType($userValues)) { + throw new \Exception(sprintf( + 'The key provided "%s" has the wrong value type. You provided %s (%s) but was expecting %s', + $this->name, print_r($userValues, true), gettype($userValues), $this->type + )); + } + } + + private function validateArray($userValues) + { + foreach ($userValues as $userValue) { + $this->itemSchema->validate($userValue); + } + } + + private function validateObject($userValues) + { + foreach ($userValues as $key => $userValue) { + $property = $this->getNestedProperty($key); + $property->validate($userValue); + } + } + + /** + * Internal method which retrieves a nested property for object parameters. + * + * @param $key The name of the child parameter + * + * @returns Parameter + * @throws \Exception + */ + private function getNestedProperty($key) + { + if (stripos($this->name, 'metadata') !== false && $this->properties instanceof Parameter) { + return $this->properties; + } elseif (isset($this->properties[$key])) { + return $this->properties[$key]; + } else { + throw new \Exception(sprintf('The key provided "%s" is not defined', $key)); + } + } + + /** + * Internal method which indicates whether the user value is of the same type as the one expected + * by this parameter. + * + * @param $userValue The value being checked + * + * @return bool + */ + private function hasCorrectType($userValue) + { + // Helper fn to see whether an array is associative (i.e. a JSON object) + $isAssociative = function ($value) { + return is_array($value) && array_keys($value) !== range(0, count($value) - 1); + }; + + // For params defined as objects, we'll let the user get away with + // passing in an associative array - since it's effectively a hash + if ($this->type == 'object' && $isAssociative($userValue)) { + return true; + } + + if (class_exists($this->type) || interface_exists($this->type)) { + return is_a($userValue, $this->type); + } + + if (!$this->type) { + return true; + } + + return gettype($userValue) == $this->type; + } + + /** + * Indicates whether this parameter represents an array type + * + * @return bool + */ + public function isArray() + { + return $this->type == 'array' && $this->itemSchema instanceof Parameter; + } + + /** + * Indicates whether this parameter represents an object type + * + * @return bool + */ + public function isObject() + { + return $this->type == 'object' && !empty($this->properties); + } + + public function getLocation() + { + return $this->location; + } + + /** + * Verifies whether the given location matches the parameter's location. + * + * @param $value + * + * @return bool + */ + public function hasLocation($value) + { + return $this->location == $value; + } + + /** + * Retrieves the parameter's path. + * + * @return string|null + */ + public function getPath() + { + return $this->path; + } + + /** + * Retrieves the common schema that an array parameter applies to all its child elements. + * + * @return Parameter + */ + public function getItemSchema() + { + return $this->itemSchema; + } + + /** + * Sets the name of the parameter to a new value + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Retrieves the child parameter for an object parameter. + * + * @param string $name The name of the child property + * + * @return null|Parameter + */ + public function getProperty($name) + { + if ($this->properties instanceof Parameter) { + $this->properties->setName($name); + return $this->properties; + } + + return isset($this->properties[$name]) ? $this->properties[$name] : null; + } + + /** + * Retrieves the prefix for a parameter, if any. + * + * @return string|null + */ + public function getPrefix() + { + return $this->prefix; + } + + public function getPrefixedName() + { + return $this->prefix . $this->getName(); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php b/server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php new file mode 100644 index 0000000..72bcfce --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php @@ -0,0 +1,67 @@ +internalState[] = $value; + } else { + $this->internalState[$offset] = $value; + } + } + + /** + * Checks whether an internal key exists. + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->internalState[$offset]); + } + + /** + * Unsets an internal key. + * + * @param string $offset + */ + public function offsetUnset($offset) + { + unset($this->internalState[$offset]); + } + + /** + * Retrieves an internal key. + * + * @param string $offset + * + * @return mixed|null + */ + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->internalState[$offset] : null; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php b/server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php new file mode 100644 index 0000000..1a36cc0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php @@ -0,0 +1,73 @@ +nextHandler = $nextHandler; + $this->tokenGenerator = $tokenGenerator; + $this->token = $token; + } + + /** + * This method is invoked before every HTTP request is sent to the API. When this happens, it + * checks to see whether a token is set and valid, and then sets the ``X-Auth-Token`` header + * for the HTTP request before letting it continue on its merry way. + * + * @param RequestInterface $request + * @param array $options + * + * @return mixed|void + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + if ($this->shouldIgnore($request)) { + return $fn($request, $options); + } + + if (!$this->token || $this->token->hasExpired()) { + $this->token = call_user_func($this->tokenGenerator); + } + + $modify = ['set_headers' => ['X-Auth-Token' => $this->token->getId()]]; + + return $fn(modify_request($request, $modify), $options); + } + + /** + * Internal method which prevents infinite recursion. For certain requests, like the initial + * auth call itself, we do NOT want to send a token. + * + * @param RequestInterface $request + * + * @return bool + */ + private function shouldIgnore(RequestInterface $request) + { + return strpos((string) $request->getUri(), 'tokens') !== false && $request->getMethod() == 'POST'; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php b/server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php new file mode 100644 index 0000000..b4ab381 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php @@ -0,0 +1,20 @@ +request = $request; + } + + public function setResponse(ResponseInterface $response) + { + $this->response = $response; + } + + public function getRequest() + { + return $this->request; + } + + public function getResponse() + { + return $this->response; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/BaseError.php b/server/vendor/php-opencloud/common/src/Common/Error/BaseError.php new file mode 100644 index 0000000..a7cb26e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/BaseError.php @@ -0,0 +1,12 @@ +client = $client ?: new Client(); + } + + /** + * Internal method used when outputting headers in the error description. + * + * @param $name + * + * @return string + */ + private function header($name) + { + return sprintf("%s\n%s\n", $name, str_repeat('~', strlen($name))); + } + + /** + * Before outputting custom links, it is validated to ensure that the user is not + * directed off to a broken link. If a 404 is detected, it is hidden. + * + * @param $link The proposed link + * + * @return bool + */ + private function linkIsValid($link) + { + $link = $this->docDomain . $link; + + try { + return $this->client->request('HEAD', $link)->getStatusCode() < 400; + } catch (ClientException $e) { + return false; + } + } + + /** + * @param MessageInterface $message + * + * @codeCoverageIgnore + * @return string + */ + public function str(MessageInterface $message) + { + if ($message instanceof RequestInterface) { + $msg = trim($message->getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + if ($message->getBody()->getSize() < ini_get('memory_limit')) { + $msg .= "\r\n\r\n" . $message->getBody(); + } + + return $msg; + } + + /** + * Helper method responsible for constructing and returning {@see BadResponseError} exceptions. + * + * @param RequestInterface $request The faulty request + * @param ResponseInterface $response The error-filled response + * + * @return BadResponseError + */ + public function httpError(RequestInterface $request, ResponseInterface $response) + { + $message = $this->header('HTTP Error'); + + $message .= sprintf("The remote server returned a \"%d %s\" error for the following transaction:\n\n", + $response->getStatusCode(), $response->getReasonPhrase()); + + $message .= $this->header('Request'); + $message .= trim($this->str($request)) . PHP_EOL . PHP_EOL; + + $message .= $this->header('Response'); + $message .= trim($this->str($response)) . PHP_EOL . PHP_EOL; + + $message .= $this->header('Further information'); + $message .= $this->getStatusCodeMessage($response->getStatusCode()); + + $message .= "Visit http://docs.php-opencloud.com/en/latest/http-codes for more information about debugging " + . "HTTP status codes, or file a support issue on https://github.com/php-opencloud/openstack/issues."; + + $e = new BadResponseError($message); + $e->setRequest($request); + $e->setResponse($response); + + return $e; + } + + private function getStatusCodeMessage($statusCode) + { + $errors = [ + 400 => 'Please ensure that your input values are valid and well-formed. ', + 401 => 'Please ensure that your authentication credentials are valid. ', + 404 => "Please ensure that the resource you're trying to access actually exists. ", + 500 => 'Please try this operation again once you know the remote server is operational. ', + ]; + + return isset($errors[$statusCode]) ? $errors[$statusCode] : ''; + } + + /** + * Helper method responsible for constructing and returning {@see UserInputError} exceptions. + * + * @param string $expectedType The type that was expected from the user + * @param mixed $userValue The incorrect value the user actually provided + * @param string|null $furtherLink A link to further information if necessary (optional). + * + * @return UserInputError + */ + public function userInputError($expectedType, $userValue, $furtherLink = null) + { + $message = $this->header('User Input Error'); + + $message .= sprintf("%s was expected, but the following value was passed in:\n\n%s\n", + $expectedType, print_r($userValue, true)); + + $message .= "Please ensure that the value adheres to the expectation above. "; + + if ($furtherLink && $this->linkIsValid($furtherLink)) { + $message .= sprintf("Visit %s for more information about input arguments. ", $this->docDomain . $furtherLink); + } + + $message .= 'If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues.'; + + return new UserInputError($message); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php b/server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php new file mode 100644 index 0000000..3e01d74 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php @@ -0,0 +1,12 @@ + $val) { + $key = isset($aliases[$key]) ? $aliases[$key] : $key; + if (property_exists($this, $key)) { + $this->$key = $val; + } + } + } + + private function set($key, $property, array $data, callable $fn = null) + { + if (isset($data[$key]) && property_exists($this, $property)) { + $value = $fn ? call_user_func($fn, $data[$key]) : $data[$key]; + $this->$property = $value; + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/JsonPath.php b/server/vendor/php-opencloud/common/src/Common/JsonPath.php new file mode 100644 index 0000000..0a6372e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/JsonPath.php @@ -0,0 +1,119 @@ +['foo' => ['bar' => ['baz' => 'some_value']]] + * + * and you wanted to insert or extract an element. Usually, you would use: + * + *
$array['foo']['bar']['baz'] = 'new_value';
+ * + * but sometimes you do not have access to the variable - so a string representation is needed. Using + * XPath-like syntax, this class allows you to do this: + * + *
$jsonPath = new JsonPath($array);
+ * $jsonPath->set('foo.bar.baz', 'new_value');
+ * $val = $jsonPath->get('foo.bar.baz');
+ * 
+ * + * @package OpenCloud\Common + */ +class JsonPath +{ + /** @var array */ + private $jsonStructure; + + /** + * @param $structure The initial data structure to extract from and insert into. Typically this will be a + * multidimensional associative array; but well-formed JSON strings are also acceptable. + */ + public function __construct($structure) + { + $this->jsonStructure = is_string($structure) ? json_decode($structure, true) : $structure; + } + + /** + * Set a node in the structure + * + * @param $path The XPath to use + * @param $value The new value of the node + */ + public function set($path, $value) + { + $this->jsonStructure = $this->setPath($path, $value, $this->jsonStructure); + } + + /** + * Internal method for recursive calls. + * + * @param $path + * @param $value + * @param $json + * @return mixed + */ + private function setPath($path, $value, $json) + { + $nodes = explode('.', $path); + $point = array_shift($nodes); + + if (!isset($json[$point])) { + $json[$point] = []; + } + + if (!empty($nodes)) { + $json[$point] = $this->setPath(implode('.', $nodes), $value, $json[$point]); + } else { + $json[$point] = $value; + } + + return $json; + } + + /** + * Return the updated structure. + * + * @return mixed + */ + public function getStructure() + { + return $this->jsonStructure; + } + + /** + * Get a path's value. If no path can be matched, NULL is returned. + * + * @param $path + * @return mixed|null + */ + public function get($path) + { + return $this->getPath($path, $this->jsonStructure); + } + + /** + * Internal method for recursion. + * + * @param $path + * @param $json + * @return null + */ + private function getPath($path, $json) + { + $nodes = explode('.', $path); + $point = array_shift($nodes); + + if (!isset($json[$point])) { + return null; + } + + if (empty($nodes)) { + return $json[$point]; + } else { + return $this->getPath(implode('.', $nodes), $json[$point]); + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php b/server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php new file mode 100644 index 0000000..2c12ec0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php @@ -0,0 +1,115 @@ +makeDiff($src, $dest); + } + + public function makeDiff($srcStruct, $desStruct, $path = '') + { + $changes = []; + + if (is_object($srcStruct)) { + $changes = $this->handleObject($srcStruct, $desStruct, $path); + } elseif (is_array($srcStruct)) { + $changes = $this->handleArray($srcStruct, $desStruct, $path); + } elseif ($srcStruct != $desStruct) { + $changes[] = $this->makePatch(self::OP_REPLACE, $path, $desStruct); + } + + return $changes; + } + + protected function handleArray($srcStruct, $desStruct, $path) + { + $changes = []; + + if ($diff = $this->arrayDiff($desStruct, $srcStruct)) { + foreach ($diff as $key => $val) { + if (is_object($val)) { + $changes = array_merge($changes, $this->makeDiff($srcStruct[$key], $val, $this->path($path, $key))); + } else { + $op = array_key_exists($key, $srcStruct) && !in_array($srcStruct[$key], $desStruct, true) + ? self::OP_REPLACE : self::OP_ADD; + $changes[] = $this->makePatch($op, $this->path($path, $key), $val); + } + } + } elseif ($srcStruct != $desStruct) { + foreach ($srcStruct as $key => $val) { + if (!in_array($val, $desStruct, true)) { + $changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key)); + } + } + } + + return $changes; + } + + protected function handleObject($srcStruct, $desStruct, $path) + { + $changes = []; + + if ($this->shouldPartiallyReplace($srcStruct, $desStruct)) { + foreach ($desStruct as $key => $val) { + if (!property_exists($srcStruct, $key)) { + $changes[] = $this->makePatch(self::OP_ADD, $this->path($path, $key), $val); + } elseif ($srcStruct->$key != $val) { + $changes = array_merge($changes, $this->makeDiff($srcStruct->$key, $val, $this->path($path, $key))); + } + } + } elseif ($this->shouldPartiallyReplace($desStruct, $srcStruct)) { + foreach ($srcStruct as $key => $val) { + if (!property_exists($desStruct, $key)) { + $changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key)); + } + } + } + + return $changes; + } + + protected function shouldPartiallyReplace($o1, $o2) + { + return count(array_diff_key((array) $o1, (array) $o2)) < count($o1); + } + + protected function arrayDiff(array $a1, array $a2) + { + $result = []; + + foreach ($a1 as $key => $val) { + if (!in_array($val, $a2, true)) { + $result[$key] = $val; + } + } + + return $result; + } + + protected function path($root, $path) + { + if ($path === '_empty_') { + $path = ''; + } + + return rtrim($root, '/') . '/' . ltrim($path, '/'); + } + + protected function makePatch($op, $path, $val = null) + { + switch ($op) { + default: + return ['op' => $op, 'path' => $path, 'value' => $val]; + case self::OP_REMOVE: + return ['op' => $op, 'path' => $path]; + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php b/server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php new file mode 100644 index 0000000..a1cd380 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php @@ -0,0 +1,72 @@ +body = (object) $body; + $this->validator = $validator ?: new Validator(); + } + + public function getPropertyPaths() + { + $paths = []; + + foreach ($this->body->properties as $propertyName => $property) { + $paths[] = sprintf("/%s", $propertyName); + } + + return $paths; + } + + public function normalizeObject($subject, array $aliases) + { + $out = new \stdClass; + + foreach ($this->body->properties as $propertyName => $property) { + $name = isset($aliases[$propertyName]) ? $aliases[$propertyName] : $propertyName; + if (isset($property->readOnly) && $property->readOnly === true) { + continue; + } elseif (property_exists($subject, $name)) { + $out->$propertyName = $subject->$name; + } elseif (property_exists($subject, $propertyName)) { + $out->$propertyName = $subject->$propertyName; + } + } + + return $out; + } + + public function validate($data) + { + $this->validator->check($data, $this->body); + } + + public function isValid() + { + return $this->validator->isValid(); + } + + public function getErrors() + { + return $this->validator->getErrors(); + } + + public function getErrorString() + { + $msg = "Provided values do not validate. Errors:\n"; + + foreach ($this->getErrors() as $error) { + $msg .= sprintf("[%s] %s\n", $error['property'], $error['message']); + } + + return $msg; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php b/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php new file mode 100644 index 0000000..9f79b07 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php @@ -0,0 +1,243 @@ + 'fooBar' + * + * will extract FOO_BAR from the response, and save it as 'fooBar' in the resource. + * + * @var array + */ + protected $aliases = []; + + /** + * Populates the current resource from a response object. + * + * @param ResponseInterface $response + * + * @return $this|ResourceInterface + */ + public function populateFromResponse(ResponseInterface $response) + { + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0) { + $json = Utils::jsonDecode($response); + if (!empty($json)) { + $this->populateFromArray(Utils::flattenJson($json, $this->resourceKey)); + } + } + + return $this; + } + + /** + * Populates the current resource from a data array. + * + * @param array $array + * + * @return mixed|void + */ + public function populateFromArray(array $array) + { + $reflClass = new \ReflectionClass($this); + + foreach ($array as $key => $val) { + $propertyName = isset($this->aliases[$key]) ? $this->aliases[$key] : $key; + + if (property_exists($this, $propertyName)) { + if ($type = $this->extractTypeFromDocBlock($reflClass, $propertyName)) { + $val = $this->parseDocBlockValue($type, $val); + } + + $this->$propertyName = $val; + } + } + } + + private function parseDocBlockValue($type, $val) + { + if (strpos($type, '[]') === 0 && is_array($val)) { + $array = []; + foreach ($val as $subVal) { + $array[] = $this->model($this->normalizeModelClass(substr($type, 2)), $subVal); + } + $val = $array; + } elseif (strcasecmp($type, '\datetimeimmutable') === 0) { + $val = new \DateTimeImmutable($val); + } elseif ($this->isNotNativeType($type)) { + $val = $this->model($this->normalizeModelClass($type), $val); + } + + return $val; + } + + private function isNotNativeType($type) + { + return !in_array($type, [ + 'string', 'bool', 'boolean', 'double', 'null', 'array', 'object', 'int', 'integer', 'float', 'numeric', + 'mixed' + ]); + } + + private function normalizeModelClass($class) + { + if (strpos($class, '\\') === false) { + $currentNamespace = (new \ReflectionClass($this))->getNamespaceName(); + $class = sprintf("%s\\%s", $currentNamespace, $class); + } + + return $class; + } + + private function extractTypeFromDocBlock(\ReflectionClass $reflClass, $propertyName) + { + $docComment = $reflClass->getProperty($propertyName)->getDocComment(); + + if (!$docComment) { + return false; + } + + $matches = []; + preg_match('#@var ((\[\])?[\w|\\\]+)#', $docComment, $matches); + return isset($matches[1]) ? $matches[1] : null; + } + + /** + * Internal method which retrieves the values of provided keys. + * + * @param array $keys + * + * @return array + */ + protected function getAttrs(array $keys) + { + $output = []; + + foreach ($keys as $key) { + if (property_exists($this, $key) && $this->$key !== null) { + $output[$key] = $this->$key; + } + } + + return $output; + } + + /** + * @param array $definition + * + * @return mixed + */ + public function executeWithState(array $definition) + { + return $this->execute($definition, $this->getAttrs(array_keys($definition['params']))); + } + + private function getResourcesKey() + { + $resourcesKey = $this->resourcesKey; + + if (!$resourcesKey) { + $class = substr(static::class, strrpos(static::class, '\\') + 1); + $resourcesKey = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $class)) . 's'; + } + + return $resourcesKey; + } + + /** + * {@inheritDoc} + */ + public function enumerate(array $def, array $userVals = [], callable $mapFn = null) + { + $operation = $this->getOperation($def); + + $requestFn = function ($marker) use ($operation, $userVals) { + if ($marker) { + $userVals['marker'] = $marker; + } + return $this->sendRequest($operation, $userVals); + }; + + $resourceFn = function (array $data) { + $resource = $this->newInstance(); + $resource->populateFromArray($data); + return $resource; + }; + + $opts = [ + 'limit' => isset($userVals['limit']) ? $userVals['limit'] : null, + 'resourcesKey' => $this->getResourcesKey(), + 'markerKey' => $this->markerKey, + 'mapFn' => $mapFn, + ]; + + $iterator = new Iterator($opts, $requestFn, $resourceFn); + return $iterator(); + } + + public function extractMultipleInstances(ResponseInterface $response, $key = null) + { + $key = $key ?: $this->getResourcesKey(); + $resourcesData = Utils::jsonDecode($response)[$key]; + + $resources = []; + + foreach ($resourcesData as $resourceData) { + $resource = $this->newInstance(); + $resource->populateFromArray($resourceData); + $resources[] = $resource; + } + + return $resources; + } + + protected function getService() + { + $class = static::class; + $service = substr($class, 0, strpos($class, 'Models') - 1) . '\\Service'; + + return new $service($this->client, $this->api); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php new file mode 100644 index 0000000..19579c1 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php @@ -0,0 +1,19 @@ + 'val3', 'Baz' => 'val4']); is called, then the resource will have the following + * metadata: + * + * Foo: val3 + * Bar: val2 + * Baz: val4 + * + * You will notice that any metadata items which are not specified in the call are preserved. + * + * @param array $metadata The new metadata items + * + * @return mixed + */ + public function mergeMetadata(array $metadata); + + /** + * Replaces all of the existing metadata items for a resource with a new set of values. Any metadata items which + * are not provided in the call are removed from the resource. For example, if the resource has this metadata + * already set: + * + * Foo: val1 + * Bar: val2 + * + * and resetMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following + * metadata: + * + * Foo: val3 + * Baz: val4 + * + * @param array $metadata The new metadata items + * + * @return mixed + */ + public function resetMetadata(array $metadata); + + /** + * Extracts metadata from a response object and returns it in the form of an associative array. + * + * @param ResponseInterface $response + * + * @return array + */ + public function parseMetadata(ResponseInterface $response); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php b/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php new file mode 100644 index 0000000..519be7e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php @@ -0,0 +1,124 @@ +retrieve(); + + if ($this->status == $status || $this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } + + /** + * Provides a blocking operation until the resource has reached a particular state. The method + * will enter a loop, executing the callback until TRUE is returned. This provides great + * flexibility. + * + * @param callable $fn An anonymous function that will be executed on every iteration. You can + * encapsulate your own logic to determine whether the resource has + * successfully transitioned. When TRUE is returned by the callback, + * the loop will end. + * @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. If FALSE + * is provided, the timeout will never be considered. + * @param int $sleepPeriod The amount of time to pause between each HTTP request. + */ + public function waitWithCallback(callable $fn, $timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + $this->retrieve(); + + $response = call_user_func_array($fn, [$this]); + + if ($response === true || $this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } + + /** + * Internal method used to identify whether a timeout has been exceeded. + * + * @param bool|int $timeout + * @param int $startTime + * + * @return bool + */ + private function shouldHalt($timeout, $startTime) + { + if ($timeout === false) { + return false; + } + + return time() - $startTime >= $timeout; + } + + /** + * Convenience method providing a blocking operation until the resource transitions to an + * ``ACTIVE`` status. + * + * @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. If FALSE + * is provided, the timeout will never be considered. + */ + public function waitUntilActive($timeout = false) + { + $this->waitUntil('ACTIVE', $timeout); + } + + public function waitUntilDeleted($timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + try { + $this->retrieve(); + } catch (BadResponseError $e) { + if ($e->getResponse()->getStatusCode() === 404) { + break; + } + throw $e; + } + + if ($this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php b/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php new file mode 100644 index 0000000..63d4455 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php @@ -0,0 +1,97 @@ +limit = isset($options['limit']) ? $options['limit'] : false; + $this->count = 0; + + if (isset($options['resourcesKey'])) { + $this->resourcesKey = $options['resourcesKey']; + } + + if (isset($options['markerKey'])) { + $this->markerKey = $options['markerKey']; + } + + if (isset($options['mapFn']) && is_callable($options['mapFn'])) { + $this->mapFn = $options['mapFn']; + } + + $this->requestFn = $requestFn; + $this->resourceFn = $resourceFn; + } + + private function fetchResources() + { + if ($this->shouldNotSendAnotherRequest()) { + return false; + } + + $response = call_user_func($this->requestFn, $this->currentMarker); + + $json = Utils::flattenJson(Utils::jsonDecode($response), $this->resourcesKey); + + if ($response->getStatusCode() === 204 || empty($json)) { + return false; + } + + return $json; + } + + private function assembleResource(array $data) + { + $resource = call_user_func($this->resourceFn, $data); + + // Invoke user-provided fn if provided + if ($this->mapFn) { + call_user_func_array($this->mapFn, [&$resource]); + } + + // Update marker if operation supports it + if ($this->markerKey) { + $this->currentMarker = $resource->{$this->markerKey}; + } + + return $resource; + } + + private function totalReached() + { + return $this->limit && $this->count >= $this->limit; + } + + private function shouldNotSendAnotherRequest() + { + return $this->totalReached() || ($this->count > 0 && !$this->markerKey); + } + + public function __invoke() + { + while ($resources = $this->fetchResources()) { + foreach ($resources as $resourceData) { + if ($this->totalReached()) { + break; + } + + $this->count++; + + yield $this->assembleResource($resourceData); + } + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php new file mode 100644 index 0000000..8e255c0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php @@ -0,0 +1,28 @@ + 'publicURL']; + + /** + * @param array $globalOptions Options that will be applied to every service created by this builder. + * Eventually they will be merged (and if necessary overridden) by the + * service-specific options passed in. + */ + public function __construct(array $globalOptions = [], $rootNamespace = 'OpenCloud') + { + $this->globalOptions = $globalOptions; + $this->rootNamespace = $rootNamespace; + } + + /** + * Internal method which resolves the API and Service classes for a service. + * + * @param string $serviceName The name of the service, e.g. Compute + * @param int $serviceVersion The major version of the service, e.g. 2 + * + * @return array + */ + private function getClasses($serviceName, $serviceVersion) + { + $rootNamespace = sprintf("%s\\%s\\v%d", $this->rootNamespace, $serviceName, $serviceVersion); + + return [ + sprintf("%s\\Api", $rootNamespace), + sprintf("%s\\Service", $rootNamespace), + ]; + } + + /** + * This method will return an OpenCloud service ready fully built and ready for use. There is + * some initial setup that may prohibit users from directly instantiating the service class + * directly - this setup includes the configuration of the HTTP client's base URL, and the + * attachment of an authentication handler. + * + * @param $serviceName The name of the service as it appears in the OpenCloud\* namespace + * @param $serviceVersion The major version of the service + * @param array $serviceOptions The service-specific options to use + * + * @return \OpenCloud\Common\Service\ServiceInterface + * + * @throws \Exception + */ + public function createService($serviceName, $serviceVersion, array $serviceOptions = []) + { + $options = $this->mergeOptions($serviceOptions); + + $this->stockIdentityService($options); + $this->stockAuthHandler($options); + $this->stockHttpClient($options, $serviceName); + + list($apiClass, $serviceClass) = $this->getClasses($serviceName, $serviceVersion); + + return new $serviceClass($options['httpClient'], new $apiClass()); + } + + private function stockHttpClient(array &$options, $serviceName) + { + if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) { + if (strcasecmp($serviceName, 'identity') === 0) { + $baseUrl = $options['authUrl']; + $stack = $this->getStack($options['authHandler']); + } else { + list($token, $baseUrl) = $options['identityService']->authenticate($options); + $stack = $this->getStack($options['authHandler'], $token); + } + + $this->addDebugMiddleware($options, $stack); + + $options['httpClient'] = $this->httpClient($baseUrl, $stack); + } + } + + /** + * @codeCoverageIgnore + */ + private function addDebugMiddleware(array $options, HandlerStack &$stack) + { + if (!empty($options['debugLog']) + && !empty($options['logger']) + && !empty($options['messageFormatter']) + ) { + $stack->push(GuzzleMiddleware::log($options['logger'], $options['messageFormatter'])); + } + } + + private function stockIdentityService(array &$options) + { + if (!isset($options['identityService'])) { + $httpClient = $this->httpClient($options['authUrl'], HandlerStack::create()); + $options['identityService'] = Service::factory($httpClient); + } + } + + /** + * @param array $options + * @codeCoverageIgnore + */ + private function stockAuthHandler(array &$options) + { + if (!isset($options['authHandler'])) { + $options['authHandler'] = function () use ($options) { + return $options['identityService']->generateToken($options); + }; + } + } + + private function getStack(callable $authHandler, Token $token = null) + { + $stack = HandlerStack::create(); + $stack->push(Middleware::authHandler($authHandler, $token)); + return $stack; + } + + private function httpClient($baseUrl, HandlerStack $stack) + { + return new Client([ + 'base_uri' => Utils::normalizeUrl($baseUrl), + 'handler' => $stack, + ]); + } + + private function mergeOptions(array $serviceOptions) + { + $options = array_merge($this->defaults, $this->globalOptions, $serviceOptions); + + if (!isset($options['authUrl'])) { + throw new \InvalidArgumentException('"authUrl" is a required option'); + } + + return $options; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php b/server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php new file mode 100644 index 0000000..6ad3089 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php @@ -0,0 +1,14 @@ +push(Middleware::httpErrors()); + $stack->push(Middleware::prepareBody()); + + return $stack; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php b/server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php new file mode 100644 index 0000000..11b31d3 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php @@ -0,0 +1,95 @@ +getName(); + if ($path = $param->getPath()) { + $jsonPath = new JsonPath($json); + $jsonPath->set(sprintf("%s.%s", $path, $name), $userValue); + $json = $jsonPath->getStructure(); + } elseif ($name) { + $json[$name] = $userValue; + } else { + $json[] = $userValue; + } + + return $json; + } + + /** + * Populates a value into an array-like structure. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * + * @return array|mixed + */ + private function stockArrayJson(Parameter $param, $userValue) + { + $elems = []; + foreach ($userValue as $item) { + $elems = $this->stockJson($param->getItemSchema(), $item, $elems); + } + return $elems; + } + + /** + * Populates a value into an object-like structure. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * + * @return array + */ + private function stockObjectJson(Parameter $param, $userValue) + { + $object = []; + foreach ($userValue as $key => $val) { + $object = $this->stockJson($param->getProperty($key), $val, $object); + } + return $object; + } + + /** + * A generic method that will populate a JSON structure with a value according to a schema. It + * supports multiple types and will delegate accordingly. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * @param array $json The existing JSON structure that will be populated + * + * @return array + */ + public function stockJson(Parameter $param, $userValue, $json) + { + if ($param->isArray()) { + $userValue = $this->stockArrayJson($param, $userValue); + } elseif ($param->isObject()) { + $userValue = $this->stockObjectJson($param, $userValue); + } + // Populate the final value + return $this->stockValue($param, $userValue, $json); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php b/server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php new file mode 100644 index 0000000..916ff22 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php @@ -0,0 +1,94 @@ +then( + function (ResponseInterface $response) use ($request, $handler) { + if ($response->getStatusCode() < 400) { + return $response; + } + throw (new Builder())->httpError($request, $response); + } + ); + }; + }; + } + + /** + * @param callable $tokenGenerator + * @param Token $token + * + * @return callable + */ + public static function authHandler(callable $tokenGenerator, Token $token = null) + { + return function (callable $handler) use ($tokenGenerator, $token) { + return new AuthHandler($handler, $tokenGenerator, $token); + }; + } + + /** + * @codeCoverageIgnore + */ + public static function history(array &$container) + { + return GuzzleMiddleware::history($container); + } + + /** + * @codeCoverageIgnore + */ + public static function retry(callable $decider, callable $delay = null) + { + return GuzzleMiddleware::retry($decider, $delay); + } + + /** + * @codeCoverageIgnore + */ + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) + { + return GuzzleMiddleware::log($logger, $formatter, $logLevel); + } + + /** + * @codeCoverageIgnore + */ + public static function prepareBody() + { + return GuzzleMiddleware::prepareBody(); + } + + /** + * @codeCoverageIgnore + */ + public static function mapRequest(callable $fn) + { + return GuzzleMiddleware::mapRequest($fn); + } + + /** + * @codeCoverageIgnore + */ + public static function mapResponse(callable $fn) + { + return GuzzleMiddleware::mapResponse($fn); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php b/server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php new file mode 100644 index 0000000..30f04af --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php @@ -0,0 +1,85 @@ +jsonSerializer = $jsonSerializer ?: new JsonSerializer(); + } + + public function serializeOptions(Operation $operation, array $userValues = []) + { + $options = ['headers' => []]; + + foreach ($userValues as $paramName => $paramValue) { + if (null === ($schema = $operation->getParam($paramName))) { + continue; + } + + $method = sprintf('stock%s', ucfirst($schema->getLocation())); + $this->$method($schema, $paramValue, $options); + } + + if (!empty($options['json'])) { + if ($key = $operation->getJsonKey()) { + $options['json'] = [$key => $options['json']]; + } + if (strpos(json_encode($options['json']), '\/') !== false) { + $options['body'] = json_encode($options['json'], JSON_UNESCAPED_SLASHES); + $options['headers']['Content-Type'] = 'application/json'; + unset($options['json']); + } + } + + return $options; + } + + private function stockUrl() + { + } + + private function stockQuery(Parameter $schema, $paramValue, array &$options) + { + $options['query'][$schema->getName()] = $paramValue; + } + + private function stockHeader(Parameter $schema, $paramValue, array &$options) + { + $paramName = $schema->getName(); + + if (stripos($paramName, 'metadata') !== false) { + return $this->stockMetadataHeader($schema, $paramValue, $options); + } + + $options['headers'] += is_scalar($paramValue) ? [$schema->getPrefixedName() => $paramValue] : []; + } + + private function stockMetadataHeader(Parameter $schema, $paramValue, array &$options) + { + foreach ($paramValue as $key => $keyVal) { + $schema = $schema->getItemSchema() ?: new Parameter(['prefix' => $schema->getPrefix(), 'name' => $key]); + $this->stockHeader($schema, $keyVal, $options); + } + } + + private function stockJson(Parameter $schema, $paramValue, array &$options) + { + $json = isset($options['json']) ? $options['json'] : []; + $options['json'] = $this->jsonSerializer->stockJson($schema, $paramValue, $json); + } + + private function stockRaw(Parameter $schema, $paramValue, array &$options) + { + $options['body'] = $paramValue; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/Utils.php b/server/vendor/php-opencloud/common/src/Common/Transport/Utils.php new file mode 100644 index 0000000..c2a2dc1 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/Utils.php @@ -0,0 +1,88 @@ + 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ]; + + $responseBody = (string) $response->getBody(); + + if (strlen($responseBody) === 0) { + return $responseBody; + } + + $data = json_decode($responseBody, $assoc); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' . (isset($jsonErrors[$last]) ? $jsonErrors[$last] : 'Unknown error') + ); + } + + return $data; + } + + /** + * Method for flattening a nested array. + * + * @param array $data The nested array + * @param null $key The key to extract + * + * @return array + */ + public static function flattenJson($data, $key = null) + { + return (!empty($data) && $key && isset($data[$key])) ? $data[$key] : $data; + } + + /** + * Method for normalize an URL string. + * + * Append the http:// prefix if not present, and add a + * closing url separator when missing. + * + * @param string $url The url representation. + * + * @return string + */ + public static function normalizeUrl($url) + { + if (strpos($url, 'http') === false) { + $url = 'http://' . $url; + } + + return rtrim($url, '/') . '/'; + } + + /** + * Add an unlimited list of paths to a given URI. + * + * @param UriInterface $uri + * @param ...$paths + * + * @return UriInterface + */ + public static function addPaths(UriInterface $uri, ...$paths) + { + return uri_for(rtrim((string) $uri, '/') . '/' . implode('/', $paths)); + } + + public static function appendPath(UriInterface $uri, $path) + { + return uri_for(rtrim((string) $uri, '/') . '/' . $path); + } +} diff --git a/server/vendor/php-opencloud/common/tests/integration/DefaultLogger.php b/server/vendor/php-opencloud/common/tests/integration/DefaultLogger.php new file mode 100644 index 0000000..a546c70 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/DefaultLogger.php @@ -0,0 +1,20 @@ +format($level, $message, $context); + } + + private function format($level, $message, $context) + { + $msg = strtr($message, $context); + + return sprintf("%s: %s\n", strtoupper($level), $msg); + } +} diff --git a/server/vendor/php-opencloud/common/tests/integration/Runner.php b/server/vendor/php-opencloud/common/tests/integration/Runner.php new file mode 100644 index 0000000..98d094c --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/Runner.php @@ -0,0 +1,112 @@ +basePath = $basePath; + $this->logger = new DefaultLogger(); + $this->assembleServicesFromSamples(); + } + + private function traverse($path) + { + return new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS); + } + + private function assembleServicesFromSamples() + { + foreach ($this->traverse($this->basePath) as $servicePath) { + if ($servicePath->isDir()) { + foreach ($this->traverse($servicePath) as $versionPath) { + $this->services[$servicePath->getBasename()][] = $versionPath->getBasename(); + } + } + } + } + + private function getOpts() + { + $opts = getopt('s:v:t:', ['service:', 'version:', 'test::', 'debug::', 'help::']); + + $getOpt = function (array $keys, $default) use ($opts) { + foreach ($keys as $key) { + if (isset($opts[$key])) { + return $opts[$key]; + } + } + return $default; + }; + + return [ + $getOpt(['s', 'service'], 'all'), + $getOpt(['n', 'version'], 'all'), + $getOpt(['t', 'test'], ''), + isset($opts['debug']) ? (int) $opts['debug'] : 0, + ]; + } + + private function getRunnableServices($service, $version) + { + $services = $this->services; + + if ($service != 'all') { + if (!isset($this->services[$service])) { + throw new \InvalidArgumentException(sprintf("%s service does not exist", $service)); + } + + $versions = ($version == 'all') ? $this->services[$service] : [$version]; + $services = [$service => $versions]; + } + + return $services; + } + + /** + * @return TestInterface + */ + private function getTest($serviceName, $version, $verbosity) + { + $namespace = (new \ReflectionClass($this))->getNamespaceName(); + $className = sprintf("%s\\%s\\%sTest", $namespace, Utils::toCamelCase($serviceName), ucfirst($version)); + + if (!class_exists($className)) { + throw new \RuntimeException(sprintf("%s does not exist", $className)); + } + + $basePath = $this->basePath . DIRECTORY_SEPARATOR . $serviceName . DIRECTORY_SEPARATOR . $version; + $smClass = sprintf("%s\\SampleManager", $namespace); + $class = new $className($this->logger, new $smClass($basePath, $verbosity)); + + if (!($class instanceof TestInterface)) { + throw new \RuntimeException(sprintf("%s does not implement TestInterface", $className)); + } + + return $class; + } + + public function runServices() + { + list($serviceOpt, $versionOpt, $testMethodOpt, $verbosityOpt) = $this->getOpts(); + + foreach ($this->getRunnableServices($serviceOpt, $versionOpt) as $serviceName => $versions) { + foreach ($versions as $version) { + $testRunner = $this->getTest($serviceName, $version, $verbosityOpt); + + if ($testMethodOpt) { + $testRunner->runOneTest($testMethodOpt); + } else { + $testRunner->runTests(); + } + + $testRunner->teardown(); + } + } + } +} diff --git a/server/vendor/php-opencloud/common/tests/integration/SampleManager.php b/server/vendor/php-opencloud/common/tests/integration/SampleManager.php new file mode 100644 index 0000000..4bd8b9a --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/SampleManager.php @@ -0,0 +1,104 @@ +basePath = $basePath; + $this->verbosity = $verbosity; + } + + public function deletePaths() + { + if (!empty($this->paths)) { + foreach ($this->paths as $path) { + unlink($path); + } + } + } + + protected function getGlobalReplacements() + { + return [ + '{userId}' => getenv('OS_USER_ID'), + '{username}' => getenv('OS_USERNAME'), + '{password}' => getenv('OS_PASSWORD'), + '{domainId}' => getenv('OS_DOMAIN_ID'), + '{authUrl}' => getenv('OS_AUTH_URL'), + '{tenantId}' => getenv('OS_TENANT_ID'), + '{region}' => getenv('OS_REGION'), + '{projectId}' => getenv('OS_PROJECT_ID'), + '{projectName}' => getenv('OS_PROJECT_NAME'), + ]; + } + + protected function getConnectionTemplate() + { + if ($this->verbosity === 1) { + $subst = <<<'EOL' +use OpenCloud\Integration\DefaultLogger; +use OpenCloud\Integration\Utils; +use GuzzleHttp\MessageFormatter; + +$options = [ + 'debugLog' => true, + 'logger' => new DefaultLogger(), + 'messageFormatter' => new MessageFormatter(), +]; +$openstack = new OpenCloud\OpenCloud(Utils::getAuthOpts($options)); +EOL; + } elseif ($this->verbosity === 2) { + $subst = <<<'EOL' +use OpenCloud\Integration\DefaultLogger; +use OpenCloud\Integration\Utils; +use GuzzleHttp\MessageFormatter; + +$options = [ + 'debugLog' => true, + 'logger' => new DefaultLogger(), + 'messageFormatter' => new MessageFormatter(MessageFormatter::DEBUG), +]; +$openstack = new OpenCloud\OpenCloud(Utils::getAuthOpts($options)); +EOL; + } else { + $subst = <<<'EOL' +use OpenCloud\Integration\Utils; + +$openstack = new OpenCloud\OpenCloud(Utils::getAuthOpts()); +EOL; + } + + return $subst; + } + + public function write($path, array $replacements) + { + $replacements = array_merge($this->getGlobalReplacements(), $replacements); + + $sampleFile = rtrim($this->basePath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $path; + + if (!file_exists($sampleFile) || !is_readable($sampleFile)) { + throw new \RuntimeException(sprintf("%s either does not exist or is not readable", $sampleFile)); + } + + $content = strtr(file_get_contents($sampleFile), $replacements); + $content = str_replace("'vendor/'", "'" . dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . "vendor'", $content); + + $subst = $this->getConnectionTemplate(); + $content = preg_replace('/\([^)]+\)/', '', $content, 1); + $content = str_replace('$openstack = new OpenCloud\OpenCloud;', $subst, $content); + + $tmp = tempnam(sys_get_temp_dir(), 'openstack'); + file_put_contents($tmp, $content); + + $this->paths[] = $tmp; + + return $tmp; + } +} diff --git a/server/vendor/php-opencloud/common/tests/integration/SampleManagerInterface.php b/server/vendor/php-opencloud/common/tests/integration/SampleManagerInterface.php new file mode 100644 index 0000000..f0fe848 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/SampleManagerInterface.php @@ -0,0 +1,10 @@ +logger = $logger; + $this->sampleManager = $sampleManager; + } + + public function teardown() + { + $this->sampleManager->deletePaths(); + } + + public function runOneTest($name) + { + if (!method_exists($this, $name)) { + throw new \InvalidArgumentException(sprintf("%s method does not exist", $name)); + } + + $this->startTimer(); + $this->$name(); + $this->outputTimeTaken(); + } + + protected function startTimer() + { + $this->startPoint = $this->lastPoint = microtime(true); + } + + private function wrapColor($message, $colorPrefix) + { + return sprintf("%s%s", $colorPrefix, $message) . "\033[0m\033[1;0m"; + } + + protected function logStep($message, array $context = []) + { + $duration = microtime(true) - $this->lastPoint; + + $stepTimeTaken = sprintf('(%s)', $this->formatSecDifference($duration)); + + if ($duration >= 10) { + $color = "\033[0m\033[1;31m"; // red + } elseif ($duration >= 2) { + $color = "\033[0m\033[1;33m"; // yellow + } else { + $color = "\033[0m\033[1;32m"; // green + } + + $message = '{timeTaken} ' . $message; + $context['{timeTaken}'] = $this->wrapColor($stepTimeTaken, $color); + + $this->logger->info($message, $context); + + $this->lastPoint = microtime(true); + } + + protected function randomStr($length = 5) + { + $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charsLen = strlen($chars); + + $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $randomString .= $chars[rand(0, $charsLen - 1)]; + } + + return 'phptest_' . $randomString; + } + + private function formatMinDifference($duration) + { + $output = ''; + + if (($minutes = floor($duration / 60)) > 0) { + $output .= $minutes . 'min' . (($minutes > 1) ? 's' : ''); + } + + if (($seconds = number_format(fmod($duration, 60), 2)) > 0) { + if ($minutes > 0) { + $output .= ' '; + } + $output .= $seconds . 's'; + } + + return $output; + } + + private function formatSecDifference($duration) + { + return number_format($duration, 2) . 's'; + } + + protected function outputTimeTaken() + { + $output = $this->formatMinDifference(microtime(true) - $this->startPoint); + + $this->logger->info('Finished all tests! Time taken: {output}.', ['{output}' => $output]); + } + + protected function sampleFile(array $replacements, $path) + { + return $this->sampleManager->write($path, $replacements); + } +} diff --git a/server/vendor/php-opencloud/common/tests/integration/TestInterface.php b/server/vendor/php-opencloud/common/tests/integration/TestInterface.php new file mode 100644 index 0000000..a89e8af --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/TestInterface.php @@ -0,0 +1,16 @@ + getenv('OS_AUTH_URL'), + 'region' => getenv('OS_REGION_NAME'), + 'user' => [ + 'id' => getenv('OS_USER_ID'), + 'password' => getenv('OS_PASSWORD'), + ], + 'scope' => [ + 'project' => [ + 'id' => getenv('OS_PROJECT_ID'), + ] + ] + ]; + } + + public static function getAuthOptsV2() + { + $httpClient = new Client([ + 'base_uri' => CommonUtils::normalizeUrl(getenv('OS_AUTH_URL')), + 'handler' => HandlerStack::create(), + ]); + return [ + 'authUrl' => getenv('OS_AUTH_URL'), + 'region' => getenv('OS_REGION_NAME'), + 'username' => getenv('OS_USERNAME'), + 'password' => getenv('OS_PASSWORD'), + 'tenantName' => getenv('OS_TENANT_NAME'), + 'identityService' => new Service($httpClient, new Api), + ]; + } + + public static function getAuthOpts(array $options = []) + { + $authOptions = getenv('OS_IDENTITY_API_VERSION') == '2.0' + ? self::getAuthOptsV2() + : self::getAuthOptsV3(); + + return array_merge($authOptions, $options); + } + + public static function toCamelCase($word, $separator = '_') + { + return str_replace($separator, '', ucwords($word, $separator)); + } +} diff --git a/server/vendor/php-opencloud/common/tests/integration/run.php b/server/vendor/php-opencloud/common/tests/integration/run.php new file mode 100644 index 0000000..5bc5548 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/run.php @@ -0,0 +1,10 @@ +runServices(); diff --git a/server/vendor/php-opencloud/common/tests/integration/script/compute_v2 b/server/vendor/php-opencloud/common/tests/integration/script/compute_v2 new file mode 100755 index 0000000..5e90b05 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/script/compute_v2 @@ -0,0 +1,3 @@ +#!/bin/bash + +php tests/integration/Runner.php -s compute -v v2 diff --git a/server/vendor/php-opencloud/common/tests/integration/script/identity_v3 b/server/vendor/php-opencloud/common/tests/integration/script/identity_v3 new file mode 100755 index 0000000..cd175b5 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/script/identity_v3 @@ -0,0 +1,3 @@ +#!/bin/bash + +php tests/integration/Runner.php -s identity -v v3 diff --git a/server/vendor/php-opencloud/common/tests/integration/script/networking_v2 b/server/vendor/php-opencloud/common/tests/integration/script/networking_v2 new file mode 100755 index 0000000..9583f68 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/script/networking_v2 @@ -0,0 +1,3 @@ +#!/bin/bash + +php tests/integration/Runner.php -s networking -v v2 diff --git a/server/vendor/php-opencloud/common/tests/integration/script/objectstore_v2 b/server/vendor/php-opencloud/common/tests/integration/script/objectstore_v2 new file mode 100755 index 0000000..3fb893a --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/integration/script/objectstore_v2 @@ -0,0 +1,3 @@ +#!/bin/bash + +php tests/integration/Runner.php -s objectstore -v v2 diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Api/OperationTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Api/OperationTest.php new file mode 100644 index 0000000..ea0f7c8 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Api/OperationTest.php @@ -0,0 +1,74 @@ +postServer(); + + $this->operation = new Operation($def); + } + + public function test_it_reveals_whether_params_are_set_or_not() + { + $this->assertFalse($this->operation->hasParam('foo')); + $this->assertTrue($this->operation->hasParam('name')); + } + + public function test_it_gets_params() + { + $this->assertInstanceOf(Parameter::class, $this->operation->getParam('name')); + } + + public function test_it_validates_params() + { + $this->assertTrue($this->operation->validate([ + 'name' => 'foo', + 'imageId' => 'bar', + 'flavorId' => 'baz', + ])); + } + + /** + * @expectedException \Exception + */ + public function test_exceptions_are_propagated() + { + $this->assertFalse($this->operation->validate([ + 'name' => true, + 'imageId' => 'bar', + 'flavorId' => 'baz', + ])); + } + + /** + * @expectedException \Exception + */ + public function test_an_exception_is_thrown_when_user_does_not_provide_required_options() + { + $this->operation->validate([]); + } + + /** + * @expectedException \Exception + */ + public function test_it_throws_exception_when_user_provides_undefined_options() + { + $userData = ['name' => 'new_server', 'undefined_opt' => 'bah']; + + $this->operation->validate($userData); + } + + public function test_it_gets_json_key() + { + $this->assertEquals('server', $this->operation->getJsonKey()); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Api/OperatorTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Api/OperatorTest.php new file mode 100644 index 0000000..43c5804 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Api/OperatorTest.php @@ -0,0 +1,137 @@ +rootFixturesDir = __DIR__; + + $this->def = [ + 'method' => 'GET', + 'path' => 'test', + 'params' => [], + ]; + + $this->operator = new TestOperator($this->client->reveal(), new ComputeV2Api()); + } + + public function test_it_returns_operations() + { + $this->assertInstanceOf( + 'OpenCloud\Common\Api\Operation', + $this->operator->getOperation($this->def, []) + ); + } + + public function test_it_sends_a_request_when_operations_are_executed() + { + $this->client->request('GET', 'test', ['headers' => []])->willReturn(new Request('GET', 'test')); + + $this->operator->execute($this->def, []); + } + + public function test_it_sends_a_request_when_async_operations_are_executed() + { + $this->client->requestAsync('GET', 'test', ['headers' => []])->willReturn(new Promise()); + + $this->operator->executeAsync($this->def, []); + } + + public function test_it_returns_a_model_instance() + { + $this->assertInstanceOf(ResourceInterface::class, $this->operator->model(TestResource::class)); + } + + public function test_it_populates_models_from_response() + { + $this->assertInstanceOf(ResourceInterface::class, $this->operator->model(TestResource::class, new Response(200))); + } + + public function test_it_populates_models_from_arrays() + { + $data = ['flavor' => [], 'image' => []]; + $this->assertInstanceOf(ResourceInterface::class, $this->operator->model(TestResource::class, $data)); + } + + public function test_it_wraps_sequential_ops_in_promise_when_async_is_appended_to_method_name() + { + $promise = $this->operator->createAsync('something'); + + $this->assertInstanceOf(Promise::class, $promise); + + $promise->then(function ($val) { + $this->assertEquals('Created something', $val); + }); + + $promise->wait(); + } + + /** + * @expectedException \RuntimeException + */ + public function test_it_throws_exception_when_async_is_called_on_a_non_existent_method() + { + $this->operator->fooAsync(); + } + + public function test_it_retrieves_base_http_url() + { + $returnedUri = uri_for('http://foo.com'); + + $this->client->getConfig('base_uri')->shouldBeCalled()->willReturn($returnedUri); + + $uri = $this->operator->testBaseUri(); + + $this->assertInstanceOf(Uri::class, $uri); + $this->assertEquals($returnedUri, $uri); + } + + /** + * @expectedException \Exception + */ + public function test_undefined_methods_result_in_error() + { + $this->operator->foo(); + } +} + +class TestResource extends AbstractResource +{ +} + +class TestOperator extends Operator +{ + public function testBaseUri() + { + return $this->getHttpBaseUrl(); + } + + public function create($str) + { + return 'Created ' . $str; + } + + public function fail() + { + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Api/ParameterTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Api/ParameterTest.php new file mode 100644 index 0000000..65d4760 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Api/ParameterTest.php @@ -0,0 +1,203 @@ +api = new ComputeV2Api(); + + $this->data = $this->api->postServer()['params']['name'] + ['name' => 'name']; + $this->param = new Parameter($this->data); + } + + /** + * @expectedException \RuntimeException + */ + public function test_exception_is_thrown_for_invalid_locations() + { + $data = $this->data; + $data['location'] = 'foo'; + new Parameter($data); + } + + public function test_it_should_provide_access_to_a_name() + { + $this->assertEquals($this->data['name'], $this->param->getName()); + } + + public function test_it_should_use_sentAs_alias_for_name_if_one_is_set() + { + $data = $this->data + ['sentAs' => 'foo']; + $param = new Parameter($data); + + $this->assertEquals($data['sentAs'], $param->getName()); + } + + public function test_it_indicates_whether_it_is_required_or_not() + { + $this->assertTrue($this->param->isRequired()); + } + + public function test_it_indicates_its_item_schema() + { + $data = $this->api->postServer()['params']['networks'] + ['name' => 'networks']; + $param = new Parameter($data); + + $this->assertInstanceOf(self::PARAMETER_CLASS, $param->getItemSchema()); + } + + public function test_it_allows_property_retrieval() + { + $definition = $this->api->postServer()['params']['networks']['items'] + ['name' => 'network']; + $param = new Parameter($definition); + + $this->assertInstanceOf(self::PARAMETER_CLASS, $param->getProperty('uuid')); + } + + public function test_it_indicates_its_path() + { + $path = 'foo.bar.baz'; + $param = new Parameter($this->data + ['path' => $path]); + + $this->assertEquals($path, $param->getPath()); + } + + public function test_it_verifies_a_given_location_with_a_boolean() + { + $this->assertFalse($this->param->hasLocation('foo')); + $this->assertTrue($this->param->hasLocation('json')); + } + + public function test_it_should_return_true_when_required_attributes_are_provided_and_match_their_definitions() + { + $this->assertTrue($this->param->validate('TestName')); + } + + /** + * @expectedException \Exception + */ + public function test_it_throws_exception_when_values_do_not_match_their_definition_types() + { + $data = $this->api->postServer()['params']['networks'] + ['name' => 'networks']; + $param = new Parameter($data); + + $param->validate('a_network!'); // should be an array + } + + /** + * @expectedException \Exception + */ + public function test_it_throws_exception_when_deeply_nested_values_have_wrong_types() + { + $data = $this->api->postServer()['params']['networks'] + ['name' => 'networks']; + + $param = new Parameter($data); + $param->validate(['name' => false]); // value should be a string, not bool + } + + public function test_metadata_properties_are_handled_differently() + { + $params = [ + 'name' => 'metadata', + 'type' => 'object', + 'properties' => [ + 'type' => 'string', + ], + ]; + + $userValues = ['some' => 'value']; + + $param = new Parameter($params); + $this->assertTrue($param->validate($userValues)); + } + + public function test_it_passes_validation_when_array_values_pass() + { + $params = [ + 'name' => 'foo', + 'type' => 'array', + 'items' => ['type' => 'string'], + ]; + + $userVals = ['1', '2', '3']; + + $param = new Parameter($params); + $this->assertTrue($param->validate($userVals)); + } + + /** + * @expectedException \Exception + */ + public function test_an_exception_is_thrown_when_an_undefined_property_is_provided() + { + $params = ['type' => 'object', 'properties' => ['foo' => ['type' => 'string']]]; + $userVals = ['bar' => 'baz']; + + $param = new Parameter($params); + $param->validate($userVals); + } + + public function test_it_passes_validation_when_all_subproperties_pass() + { + $params = ['type' => 'object', 'properties' => ['foo' => ['type' => 'string']]]; + $userVals = ['foo' => 'baz']; + + $param = new Parameter($params); + $this->assertTrue($param->validate($userVals)); + } + + public function test_it_sets_name() + { + $this->param->setName('foo'); + $this->assertEquals($this->param->getName(), 'foo'); + } + + public function test_it_gets_property() + { + $property = new Parameter([ + 'name' => 'metadata', + 'properties' => [ + 'type' => 'string', + 'prefix' => 'foo', + ], + ]); + + $prop = $property->getProperty('metadata'); + + $this->assertInstanceOf(Parameter::class, $prop); + $this->assertEquals('foo', $prop->getPrefix()); + } + + public function test_it_gets_prefixed_name() + { + $property = new Parameter([ + 'name' => 'metadata', + 'prefix' => 'foo-', + ]); + + $this->assertEquals('foo-metadata', $property->getPrefixedName()); + } + + /** + * @expectedException \Exception + */ + public function test_exception_is_thrown_when_value_is_not_in_enum_list() + { + $data = $this->data; + $data['enum'] = ['foo']; + + $param = new Parameter($data); + $param->validate('blah'); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Api/fixtures/headers.php b/server/vendor/php-opencloud/common/tests/unit/Common/Api/fixtures/headers.php new file mode 100644 index 0000000..f428992 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Api/fixtures/headers.php @@ -0,0 +1,25 @@ + 'POST', + 'path' => 'something', + 'params' => [ + 'name' => [ + 'type' => 'string', + 'location' => 'header', + 'sentAs' => 'X-Foo-Name' + ], + 'age' => [ + 'type' => 'integer', + 'location' => 'header' + ], + 'metadata' => [ + 'type' => 'object', + 'location' => 'header', + 'items' => [ + 'prefix' => 'X-Meta-' + ] + ], + 'other' => ['type' => 'string'] // should not be a header + ], +]; diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Api/fixtures/jsonBody.php b/server/vendor/php-opencloud/common/tests/unit/Common/Api/fixtures/jsonBody.php new file mode 100644 index 0000000..350affd --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Api/fixtures/jsonBody.php @@ -0,0 +1,27 @@ + 'POST', + 'path' => 'something', + 'params' => [ + 'name' => [ + 'type' => 'string', + 'sentAs' => 'server_name', + ], + 'other' => [ + 'type' => 'array', + 'sentAs' => 'other_params', + 'items' => [ + 'type' => 'string' + ] + ], + 'etc' => [ + 'type' => 'object', + 'sentAs' => 'etcetc', + 'properties' => [ + 'dob' => ['type' => 'string'], + 'age' => ['type' => 'integer', 'sentAs' => 'current_age'], + ] + ], + ], +]; diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/ArrayAccessTraitTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/ArrayAccessTraitTest.php new file mode 100644 index 0000000..f5d1fdb --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/ArrayAccessTraitTest.php @@ -0,0 +1,59 @@ +aa = new ArrayAccess(); + } + + public function test_offset_is_set() + { + $this->aa->offsetSet('foo', 'bar'); + $this->assertEquals(['foo' => 'bar'], $this->aa->getElements()); + } + + public function test_it_appends_if_no_key_is_set() + { + $this->aa->offsetSet(null, 'bar'); + $this->assertEquals(['bar'], $this->aa->getElements()); + } + + public function test_if_checks_if_offset_exists() + { + $this->aa->offsetSet('bar', 'foo'); + $this->assertTrue($this->aa->offsetExists('bar')); + $this->assertFalse($this->aa->offsetExists('baz')); + } + + public function test_if_gets_offset() + { + $this->aa->offsetSet('bar', 'foo'); + $this->assertEquals('foo', $this->aa->offsetGet('bar')); + $this->assertNull($this->aa->offsetGet('baz')); + } + + public function test_it_unsets_offset() + { + $this->aa->offsetSet('bar', 'foo'); + $this->aa->offsetUnset('bar'); + $this->assertNull($this->aa->offsetGet('bar')); + } +} + +class ArrayAccess +{ + use ArrayAccessTrait; + + public function getElements() + { + return $this->internalState; + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Auth/AuthHandlerTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Auth/AuthHandlerTest.php new file mode 100644 index 0000000..3c77dde --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Auth/AuthHandlerTest.php @@ -0,0 +1,59 @@ +generator = function () { + $token = $this->prophesize(FakeToken::class); + $token->getId()->shouldBeCalled()->willReturn(self::TOKEN_ID); + return $token->reveal(); + }; + + $this->handler = function (RequestInterface $r) { + return $r; + }; + + $this->handler = new AuthHandler($this->handler, $this->generator); + } + + public function test_it_should_bypass_auth_http_requests() + { + // Fake a Keystone request + $request = new Request('POST', 'https://my-openstack.org:5000/v2.0/tokens'); + + $this->assertEquals($request, call_user_func_array($this->handler, [$request, []])); + } + + public function test_it_should_generate_a_new_token_if_the_current_token_is_either_expired_or_not_set() + { + $token = $this->prophesize(Token::class); + + // force the mock token to indicate that its expired + $token->getId()->willReturn(''); + $token->hasExpired()->willReturn(true); + + $request = new Request('GET', ''); + + $handler = new AuthHandler($this->handler, $this->generator, $token->reveal()); + $handler($request, []); + } +} + +class FakeToken implements Token { + public function getId() {} + public function hasExpired() {} +} \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/DateTime.php b/server/vendor/php-opencloud/common/tests/unit/Common/DateTime.php new file mode 100644 index 0000000..512ba30 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/DateTime.php @@ -0,0 +1,16 @@ +format(self::ISO8601); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Error/BadResponseErrorTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Error/BadResponseErrorTest.php new file mode 100644 index 0000000..7aa4bf1 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Error/BadResponseErrorTest.php @@ -0,0 +1,34 @@ +e = new BadResponseError(); + } + + public function test_it_gets_request() + { + $r = new Request('GET', ''); + + $this->e->setRequest($r); + $this->assertEquals($this->e->getRequest(), $r); + } + + public function test_it_gets_response() + { + $r = new Response(500); + + $this->e->setResponse($r); + $this->assertEquals($this->e->getResponse(), $r); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Error/BuilderTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Error/BuilderTest.php new file mode 100644 index 0000000..e23806d --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Error/BuilderTest.php @@ -0,0 +1,117 @@ +client = $this->prophesize(ClientInterface::class); + $this->builder = new Builder($this->client->reveal()); + } + + public function test_it_injects_client() + { + $this->assertInstanceOf(Builder::class, new Builder($this->client->reveal())); + } + + public function test_it_builds_http_errors() + { + $request = new Request('POST', '/servers'); + $response = new Response(400, [], stream_for('Invalid parameters')); + + $requestStr = trim($this->builder->str($request)); + $responseStr = trim($this->builder->str($response)); + + $errorMessage = <<setRequest($request); + $e->setResponse($response); + + $this->assertEquals($e, $this->builder->httpError($request, $response)); + } + + public function test_it_builds_user_input_errors() + { + $expected = 'A well-formed string'; + $value = ['foo' => true]; + $link = 'http://docs.php-opencloud.com/en/latest/index.html'; + + $errorMessage = << 1 +) + +Please ensure that the value adheres to the expectation above. Visit $link for more information about input arguments. If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues. +EOT; + + $this->client + ->request('HEAD', $link) + ->shouldBeCalled() + ->willReturn(new Response(200)); + + $e = new UserInputError($errorMessage); + + $this->assertEquals($e, $this->builder->userInputError($expected, $value, 'index.html')); + } + + public function test_dead_links_are_ignored() + { + $expected = 'A well-formed string'; + $value = ['foo' => true]; + + $errorMessage = << 1 +) + +Please ensure that the value adheres to the expectation above. If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues. +EOT; + + $this->client + ->request('HEAD', 'http://docs.php-opencloud.com/en/latest/sdffsda') + ->shouldBeCalled() + ->willThrow(ClientException::class); + + $e = new UserInputError($errorMessage); + + $this->assertEquals($e, $this->builder->userInputError($expected, $value, 'sdffsda')); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/HydratorStrategyTraitTes.php b/server/vendor/php-opencloud/common/tests/unit/Common/HydratorStrategyTraitTes.php new file mode 100644 index 0000000..6841002 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/HydratorStrategyTraitTes.php @@ -0,0 +1,52 @@ +fixture = new Fixture(); + } + + public function test_it_hydrates() + { + $data = ['foo' => 1, 'bar' => 2, 'baz' => 3, 'boo' => 4]; + + $this->fixture->hydrate($data); + + $this->assertEquals(1, $this->fixture->foo); + $this->assertEquals(2, $this->fixture->getBar()); + $this->assertEquals(3, $this->fixture->getBaz()); + } + + public function test_it_hydrates_aliases() + { + $this->fixture->hydrate(['FOO!' => 1], ['FOO!' => 'foo']); + + $this->assertEquals(1, $this->fixture->foo); + } +} + +class Fixture +{ + public $foo; + protected $bar; + private $baz; + + use HydratorStrategyTrait; + + public function getBar() + { + return $this->bar; + } + public function getBaz() + { + return $this->baz; + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/JsonPathTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/JsonPathTest.php new file mode 100644 index 0000000..1410076 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/JsonPathTest.php @@ -0,0 +1,70 @@ +jsonPath = new JsonPath([]); + } + + public function test_it_sets_values_according_to_paths() + { + $this->jsonPath->set('foo.bar.baz', 'VALUE'); + + $expected = [ + 'foo' => [ + 'bar' => [ + 'baz' => 'VALUE', + ] + ] + ]; + + $this->assertEquals($expected, $this->jsonPath->getStructure()); + } + + public function test_it_sets_arrays_according_to_paths() + { + $jsonPath = new JsonPath([ + 'foo' => [ + 'bar' => [ + 'value' => 'VALUE', + ] + ] + ]); + + $jsonPath->set('foo.bar.items', ['item_1', 'item_2']); + + $expected = [ + 'foo' => [ + 'bar' => [ + 'value' => 'VALUE', + 'items' => ['item_1', 'item_2'], + ] + ] + ]; + + $this->assertEquals($expected, $jsonPath->getStructure()); + } + + public function test_it_gets_values_according_to_paths() + { + $jsonPath = new JsonPath([ + 'foo' => [ + 'bar' => [ + 'baz' => 'VALUE_1', + 'lol' => 'VALUE_2', + ] + ] + ]); + + $this->assertEquals('VALUE_1', $jsonPath->get('foo.bar.baz')); + $this->assertEquals('VALUE_2', $jsonPath->get('foo.bar.lol')); + $this->assertNull($jsonPath->get('foo.bar.boo')); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/Fixtures/jsonPatchTests.json b/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/Fixtures/jsonPatchTests.json new file mode 100644 index 0000000..e7f3579 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/Fixtures/jsonPatchTests.json @@ -0,0 +1,231 @@ +[ + { "comment": "empty list, empty docs", + "doc": {}, + "patch": [], + "expected": {} }, + + { "comment": "empty patch list", + "doc": {"foo": 1}, + "patch": [], + "expected": {"foo": 1} }, + + { "comment": "rearrangements OK?", + "doc": {"foo": 1, "bar": 2}, + "patch": [], + "expected": {"bar":2, "foo": 1} }, + + { "comment": "rearrangements OK? How about one level down ... array", + "doc": [{"foo": 1, "bar": 2}], + "patch": [], + "expected": [{"bar":2, "foo": 1}] }, + + { "comment": "rearrangements OK? How about one level down...", + "doc": {"foo":{"foo": 1, "bar": 2}}, + "patch": [], + "expected": {"foo":{"bar":2, "foo": 1}} }, + + { "comment": "toplevel array", + "doc": [], + "patch": [{"op": "add", "path": "/0", "value": "foo"}], + "expected": ["foo"] }, + + { "comment": "toplevel array, no change", + "doc": ["foo"], + "patch": [], + "expected": ["foo"] }, + + { "comment": "toplevel object, numeric string", + "doc": {}, + "patch": [{"op": "add", "path": "/foo", "value": "1"}], + "expected": {"foo":"1"} }, + + { "comment": "toplevel object, integer", + "doc": {}, + "patch": [{"op": "add", "path": "/foo", "value": 1}], + "expected": {"foo":1} }, + + { "comment": "Toplevel scalar values OK?", + "doc": "foo", + "patch": [{"op": "replace", "path": "", "value": "bar"}], + "expected": "bar", + "disabled": true }, + + { "comment": "Add, / target", + "doc": {}, + "patch": [ {"op": "add", "path": "/", "value":1 } ], + "expected": {"":1} }, + + { "comment": "Add composite value at top level", + "doc": {"foo": 1}, + "patch": [{"op": "add", "path": "/bar", "value": [1, 2]}], + "expected": {"foo": 1, "bar": [1, 2]} }, + + { "comment": "Add into composite value", + "doc": {"foo": 1, "baz": [{"qux": "hello"}]}, + "patch": [{"op": "add", "path": "/baz/0/foo", "value": "world"}], + "expected": {"foo": 1, "baz": [{"qux": "hello", "foo": "world"}]} }, + + { "doc": {"bar": [1, 2]}, + "patch": [{"op": "add", "path": "/bar/8", "value": "5"}], + "error": "Out of bounds (upper)" }, + + { "doc": {"bar": [1, 2]}, + "patch": [{"op": "add", "path": "/bar/-1", "value": "5"}], + "error": "Out of bounds (lower)" }, + + { "doc": {"foo": 1}, + "patch": [{"op": "add", "path": "/bar", "value": true}], + "expected": {"foo": 1, "bar": true} }, + + { "doc": {"foo": 1}, + "patch": [{"op": "add", "path": "/bar", "value": false}], + "expected": {"foo": 1, "bar": false} }, + + { "doc": {"foo": 1}, + "patch": [{"op": "add", "path": "/bar", "value": null}], + "expected": {"foo": 1, "bar": null} }, + + { "comment": "0 can be an array index or object element name", + "doc": {"foo": 1}, + "patch": [{"op": "add", "path": "/0", "value": "bar"}], + "expected": {"foo": 1, "0": "bar" } }, + + { "doc": ["foo"], + "patch": [{"op": "add", "path": "/1", "value": "bar"}], + "expected": ["foo", "bar"] }, + + { "doc": ["foo", "sil"], + "patch": [{"op": "add", "path": "/1", "value": "bar"}], + "expected": ["foo", "bar", "sil"] }, + + { "doc": ["foo", "sil"], + "patch": [{"op": "add", "path": "/0", "value": "bar"}], + "expected": ["bar", "foo", "sil"] }, + + { "comment": "push item to array via last index + 1", + "doc": ["foo", "sil"], + "patch": [{"op":"add", "path": "/2", "value": "bar"}], + "expected": ["foo", "sil", "bar"] }, + + { "comment": "add item to array at index > length should fail", + "doc": ["foo", "sil"], + "patch": [{"op":"add", "path": "/3", "value": "bar"}], + "error": "index is greater than number of items in array" }, + + { "doc": ["foo", "sil"], + "patch": [{"op": "add", "path": "/bar", "value": 42}], + "error": "Object operation on array target" }, + + { "doc": ["foo", "sil"], + "patch": [{"op": "add", "path": "/1", "value": ["bar", "baz"]}], + "expected": ["foo", ["bar", "baz"], "sil"], + "comment": "value in array add not flattened" }, + + { "doc": {"foo": 1, "bar": [1, 2, 3, 4]}, + "patch": [{"op": "remove", "path": "/bar"}], + "expected": {"foo": 1} }, + + { "doc": {"foo": 1, "baz": [{"qux": "hello"}]}, + "patch": [{"op": "remove", "path": "/baz/0/qux"}], + "expected": {"foo": 1, "baz": [{}]} }, + + { "doc": {"foo": 1, "baz": [{"qux": "hello"}]}, + "patch": [{"op": "replace", "path": "/foo", "value": [1, 2, 3, 4]}], + "expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]} }, + + { "doc": {"foo": [1, 2, 3, 4], "baz": [{"qux": "hello"}]}, + "patch": [{"op": "replace", "path": "/baz/0/qux", "value": "world"}], + "expected": {"foo": [1, 2, 3, 4], "baz": [{"qux": "world"}]} }, + + { "doc": ["foo"], + "patch": [{"op": "replace", "path": "/0", "value": "bar"}], + "expected": ["bar"] }, + + { "doc": [""], + "patch": [{"op": "replace", "path": "/0", "value": 0}], + "expected": [0] }, + + { "doc": [""], + "patch": [{"op": "replace", "path": "/0", "value": true}], + "expected": [true] }, + + { "doc": [""], + "patch": [{"op": "replace", "path": "/0", "value": false}], + "expected": [false] }, + + { "doc": [""], + "patch": [{"op": "replace", "path": "/0", "value": null}], + "expected": [null] }, + + { "doc": ["foo", "sil"], + "patch": [{"op": "replace", "path": "/1", "value": ["bar", "baz"]}], + "expected": ["foo", ["bar", "baz"]], + "comment": "value in array replace not flattened" }, + + { "comment": "replace whole document", + "disabled": true, + "doc": {"foo": "bar"}, + "patch": [{"op": "replace", "path": "", "value": {"baz": "qux"}}], + "expected": {"baz": "qux"} }, + + { "doc": {"foo": null}, + "patch": [{"op": "replace", "path": "/foo", "value": "truthy"}], + "expected": {"foo": "truthy"}, + "comment": "null value should be valid obj property to be replaced with something truthy" }, + + { "doc": {"foo": null}, + "patch": [{"op": "remove", "path": "/foo"}], + "expected": {}, + "comment": "null value should be valid obj property to be removed" }, + + { "doc": {"foo": "bar"}, + "patch": [{"op": "replace", "path": "/foo", "value": null}], + "expected": {"foo": null}, + "comment": "null value should still be valid obj property replace other value" }, + + { "comment": "test remove with bad number should fail", + "doc": {"foo": 1, "baz": [{"qux": "hello"}]}, + "patch": [{"op": "remove", "path": "/baz/1e0/qux"}], + "error": "remove op shouldn't remove from array with bad number" }, + + { "comment": "test remove on array", + "doc": [1, 2, 3, 4], + "patch": [{"op": "remove", "path": "/0"}], + "expected": [2, 3, 4] }, + + { "comment": "test repeated removes", + "doc": [1, 2, 3, 4], + "patch": [{ "op": "remove", "path": "/1" }, + { "op": "remove", "path": "/3" }], + "expected": [1, 3] }, + + { "comment": "test remove with bad index should fail", + "doc": [1, 2, 3, 4], + "patch": [{"op": "remove", "path": "/1e0"}], + "error": "remove op shouldn't remove from array with bad number" }, + + { "comment": "test replace with bad number should fail", + "doc": [""], + "patch": [{"op": "replace", "path": "/1e0", "value": false}], + "error": "replace op shouldn't replace in array with bad number" }, + + { "comment": "test add with bad number should fail", + "doc": ["foo", "sil"], + "patch": [{"op": "add", "path": "/1e0", "value": "bar"}], + "error": "add op shouldn't add to array with bad number" }, + + { "comment": "missing 'value' parameter to add", + "doc": [ 1 ], + "patch": [ { "op": "add", "path": "/-" } ], + "error": "missing 'value' parameter" }, + + { "comment": "missing 'value' parameter to replace", + "doc": [ 1 ], + "patch": [ { "op": "replace", "path": "/0" } ], + "error": "missing 'value' parameter" }, + + { "comment": "unrecognized op should fail", + "doc": {"foo": 1}, + "patch": [{"op": "spam", "path": "/foo", "value": 1}], + "error": "Unrecognized op 'spam'" } +] \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/JsonPatchTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/JsonPatchTest.php new file mode 100644 index 0000000..ee7db9e --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/JsonPatchTest.php @@ -0,0 +1,28 @@ +disabled) || !isset($fixture->expected)) { + continue; + } + + $actual = JsonPatch::diff($fixture->doc, $fixture->expected); + + $this->assertEquals( + json_encode($fixture->patch, JSON_UNESCAPED_SLASHES), + json_encode($actual, JSON_UNESCAPED_SLASHES), + isset($fixture->comment) ? sprintf("Failed asserting test: %s\n", $fixture->comment) : '' + ); + } + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/SchemaTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/SchemaTest.php new file mode 100644 index 0000000..22663ef --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/JsonSchema/SchemaTest.php @@ -0,0 +1,102 @@ +body = [ + 'properties' => [ + 'foo' => (object)[], + 'bar' => (object)[], + 'baz' => (object)['readOnly' => true], + ], + ]; + + $this->validator = $this->prophesize(Validator::class); + $this->schema = new Schema($this->body, $this->validator->reveal()); + } + + public function test_it_gets_errors() + { + $this->validator->getErrors() + ->shouldBeCalled() + ->willReturn([]); + + $this->assertEquals([], $this->schema->getErrors()); + } + + public function test_it_gets_error_string() + { + $this->validator->getErrors() + ->shouldBeCalled() + ->willReturn([['property' => 'foo', 'message' => 'bar']]); + + $errorMsg = sprintf("Provided values do not validate. Errors:\n[foo] bar\n"); + + $this->assertEquals($errorMsg, $this->schema->getErrorString()); + } + + public function test_it_gets_property_paths() + { + $this->assertEquals(['/foo', '/bar', '/baz'], $this->schema->getPropertyPaths()); + } + + public function test_it_ignores_readOnly_attrs() + { + $expected = (object)[ + 'foo' => true, + 'bar' => false, + ]; + + $subject = (object)[ + 'foo' => true, + 'bar' => false, + 'baz' => true, + ]; + + $this->assertEquals((object)$expected, $this->schema->normalizeObject((object)$subject, [])); + } + + public function test_it_stocks_aliases() + { + $subject = (object)[ + 'fooAlias' => true, + 'bar' => false, + 'other' => true, + ]; + + $expected = (object)[ + 'foo' => true, + 'bar' => false, + ]; + + $this->assertEquals($expected, $this->schema->normalizeObject($subject, ['foo' => 'fooAlias', 'bar' => 'lol'])); + } + + public function test_it_validates() + { + $this->validator->check([], (object) $this->body)->shouldBeCalled(); + + $this->schema->validate([]); + } + + public function test_it_checks_validity() + { + $this->validator->isValid()->shouldBeCalled(); + + $this->schema->isValid(); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Resource/AbstractResourceTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/AbstractResourceTest.php new file mode 100644 index 0000000..f334292 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/AbstractResourceTest.php @@ -0,0 +1,161 @@ +rootFixturesDir = __DIR__; + $this->resource = new TestResource($this->client->reveal(), new ComputeV2Api()); + } + + public function test_it_populates_from_response() + { + $response = new Response(200, ['Content-Type' => 'application/json'], stream_for( + json_encode(['foo' => ['bar' => '1']]) + )); + + $this->resource->populateFromResponse($response); + + $this->assertEquals('1', $this->resource->bar); + } + + public function test_it_populates_datetimes_from_arrays() + { + $dt = new \DateTimeImmutable('2015'); + + $this->resource->populateFromArray(['created' => '2015']); + + $this->assertEquals($this->resource->created, $dt); + } + + public function test_it_populates_arrays_from_arrays() + { + $this->resource->populateFromArray(['children' => [$this->resource, $this->resource]]); + + $this->assertInstanceOf(TestResource::class, $this->resource->children[0]); + } + + public function test_it_gets_attrs() + { + $this->resource->bar = 'foo'; + + $this->assertEquals(['bar' => 'foo'], $this->resource->getAttrs(['bar'])); + } + + public function test_it_executes_with_state() + { + $this->resource->id = 'foo'; + $this->resource->bar = 'bar'; + + $expectedJson = ['id' => 'foo', 'bar' => 'bar']; + + $this->setupMock('GET', 'foo', $expectedJson, [], new Response(204)); + + $this->resource->executeWithState((new ComputeV2Api())->test()); + } + + public function test_it_executes_operations_until_a_204_is_received() + { + $this->client + ->request('GET', 'servers', ['headers' => []]) + ->shouldBeCalled() + ->willReturn($this->getFixture('servers-page1')); + + $this->client + ->request('GET', 'servers', ['query' => ['marker' => '5'], 'headers' => []]) + ->shouldBeCalled() + ->willReturn(new Response(204)); + + $count = 0; + + $api = new ComputeV2Api(); + + foreach ($this->resource->enumerate($api->getServers()) as $item) { + $count++; + $this->assertInstanceOf(TestResource::class, $item); + } + + $this->assertEquals(5, $count); + } + + public function test_it_invokes_function_if_provided() + { + $this->client + ->request('GET', 'servers', ['headers' => []]) + ->shouldBeCalled() + ->willReturn($this->getFixture('servers-page1')); + + $this->client + ->request('GET', 'servers', ['query' => ['marker' => '5'], 'headers' => []]) + ->shouldBeCalled() + ->willReturn(new Response(204)); + + $api = new ComputeV2Api(); + + $count = 0; + + $fn = function () use (&$count) { + $count++; + }; + + foreach ($this->resource->enumerate($api->getServers(), [], $fn) as $item) { + } + + $this->assertEquals(5, $count); + } + + public function test_it_halts_when_user_provided_limit_is_reached() + { + $this->client + ->request('GET', 'servers', ['query' => ['limit' => 2], 'headers' => []]) + ->shouldBeCalled() + ->willReturn($this->getFixture('servers-page1')); + + $count = 0; + + $api = new ComputeV2Api(); + + foreach ($this->resource->enumerate($api->getServers(), ['limit' => 2]) as $item) { + $count++; + } + + $this->assertEquals(2, $count); + } +} + +class TestResource extends AbstractResource +{ + protected $resourceKey = 'foo'; + protected $resourcesKey = 'servers'; + protected $markerKey = 'id'; + + /** @var string */ + public $bar; + + public $id; + + /** @var \DateTimeImmutable */ + public $created; + + /** @var []TestResource */ + public $children; + + public function getAttrs(array $keys) + { + return parent::getAttrs($keys); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-empty.resp b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-empty.resp new file mode 100644 index 0000000..655a35b --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-empty.resp @@ -0,0 +1,6 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "servers": [] +} \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-page1.resp b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-page1.resp new file mode 100644 index 0000000..adebcaf --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-page1.resp @@ -0,0 +1,77 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "servers": [ + { + "id": "1", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server1" + }, + { + "id": "2", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server2" + }, + { + "id": "3", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server3" + }, + { + "id": "4", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server4" + }, + { + "id": "5", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server5" + } + ] +} \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-page2.resp b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-page2.resp new file mode 100644 index 0000000..6be6b04 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Resource/Fixtures/servers-page2.resp @@ -0,0 +1,77 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "servers": [ + { + "id": "6", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server6" + }, + { + "id": "7", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server7" + }, + { + "id": "8", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server8" + }, + { + "id": "9", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server9" + }, + { + "id": "10", + "links": [ + { + "href": "http://openstack.example.com/v2/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "self" + }, + { + "href": "http://openstack.example.com/openstack/servers/616fb98f-46ca-475e-917e-2563e5a8cd19", + "rel": "bookmark" + } + ], + "name": "server10" + } + ] +} \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Service/BuilderTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Service/BuilderTest.php new file mode 100644 index 0000000..09e9918 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Service/BuilderTest.php @@ -0,0 +1,164 @@ +builder = new Builder([]); + + $this->opts = [ + 'username' => '1', + 'password' => '2', + 'tenantId' => '3', + 'authUrl' => '4', + 'region' => '5', + 'catalogName' => '6', + 'catalogType' => '7', + ]; + } + + /** + * @expectedException \Exception + */ + public function test_it_throws_exception_if_username_is_missing() + { + $this->builder->createService('Compute', 2, []); + } + + /** + * @expectedException \Throwable + */ + public function test_it_throws_exception_if_password_is_missing() + { + $this->builder->createService('Compute', 2, ['username' => 1]); + } + + /** + * @expectedException \Throwable + */ + public function test_it_throws_exception_if_both_tenantId_and_tenantName_is_missing() + { + $this->builder->createService('Compute', 2, [ + 'username' => 1, 'password' => 2, 'authUrl' => 4, 'region' => 5, 'catalogName' => 6, 'catalogType' => 7, + ]); + } + + /** + * @expectedException \Throwable + */ + public function test_it_throws_exception_if_authUrl_is_missing() + { + $this->builder->createService('Compute', 2, ['username' => 1, 'password' => 2, 'tenantId' => 3]); + } + + /** + * @expectedException \Throwable + */ + public function test_it_throws_exception_if_region_is_missing() + { + $this->builder->createService('Compute', 2, [ + 'username' => 1, 'password' => 2, 'tenantId' => 3, 'authUrl' => 4, + ]); + } + + /** + * @expectedException \Throwable + */ + public function test_it_throws_exception_if_catalogName_is_missing() + { + $this->builder->createService('Compute', 2, [ + 'username' => 1, 'password' => 2, 'tenantId' => 3, 'authUrl' => 4, + ]); + } + + /** + * @expectedException \Throwable + */ + public function test_it_throws_exception_if_catalogType_is_missing() + { + $this->builder->createService('Compute', 2, [ + 'username' => 1, 'password' => 2, 'tenantId' => 3, 'authUrl' => 4, 'region' => 5, 'catalogName' => 6, + ]); + } + +// public function test_it_builds_services_with_custom_identity_service() +// { +// $this->rootFixturesDir = dirname(dirname(__DIR__)) . '/Identity/v2/'; +// +// $token = $this->prophesize(FakeToken::class)->reveal(); +// $service = $this->prophesize(IdentityService::class); +// $service->authenticate(Argument::type('array'))->shouldBeCalled()->willReturn([$token, '']); +// +// $this->opts += [ +// 'identityService' => $service->reveal(), +// 'catalogName' => 'nova', +// 'catalogType' => 'compute', +// 'region' => 'RegionOne', +// ]; +// +// $service = $this->builder->createService('Compute', 2, $this->opts); +// $this->assertInstanceOf(ComputeV2::class, $service); +// } + + private function setupHttpClient() + { + $this->rootFixturesDir = dirname(dirname(__DIR__)) . '/Identity/v3/'; + + $response = $this->getFixture('token-get'); + + $expectedJson = [ + 'auth' => [ + 'identity' => [ + 'methods' => ['password'], + 'password' => ['user' => ['id' => '0ca8f6', 'password' => 'secretsecret']] + ] + ] + ]; + + $httpClient = $this->prophesize(ClientInterface::class); + $httpClient->request('POST', 'tokens', ['json' => $expectedJson])->shouldBeCalled()->willReturn($response); + + return $httpClient; + } + + public function it_builds_services_with_default_identity() + { + $httpClient = $this->setupHttpClient(); + + $options = [ + 'httpClient' => $httpClient->reveal(), + 'catalogName' => 'nova', + 'catalogType' => 'compute', + 'region' => 'RegionOne', + 'user' => [ + 'id' => '0ca8f6', + 'password' => 'secretsecret', + ] + ]; + + $service = $this->builder->createService('Compute', 2, $options); + $this->assertInstanceOf(ComputeV2::class, $service); + } + +// public function test_it_does_not_authenticate_when_creating_identity_services() +// { +// $this->assertInstanceOf(IdentityV3::class, $this->builder->createService('Identity', 3, [ +// 'authUrl' => 'foo.com', +// ])); +// } +} \ No newline at end of file diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Transport/HandlerStackTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/HandlerStackTest.php new file mode 100644 index 0000000..ec1cf85 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/HandlerStackTest.php @@ -0,0 +1,15 @@ +assertInstanceOf(HandlerStack::class, HandlerStack::create(new MockHandler())); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Transport/JsonSerializerTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/JsonSerializerTest.php new file mode 100644 index 0000000..9075594 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/JsonSerializerTest.php @@ -0,0 +1,85 @@ +serializer = new JsonSerializer(); + } + + public function test_it_embeds_params_according_to_path() + { + $param = $this->prophesize(Parameter::class); + $param->isArray()->shouldBeCalled()->willReturn(false); + $param->isObject()->shouldBeCalled()->willReturn(false); + $param->getName()->shouldBeCalled()->willReturn('username'); + $param->getPath()->shouldBeCalled()->willReturn('auth.passwordCredentials'); + + $userValue = 'fooBar'; + + $expected = [ + 'auth' => [ + 'passwordCredentials' => [ + 'username' => $userValue, + ], + ], + ]; + + $actual = $this->serializer->stockJson($param->reveal(), $userValue, []); + + $this->assertEquals($expected, $actual); + } + + public function test_it_serializes_arrays() + { + $param = $this->prophesize(Parameter::class); + $param->isArray()->shouldBeCalled()->willReturn(true); + $param->getName()->shouldBeCalled()->willReturn('fooBar'); + $param->getPath()->shouldBeCalled()->willReturn(false); + + $itemSchema = $this->prophesize(Parameter::class); + $itemSchema->isArray()->shouldBeCalled()->willReturn(false); + $itemSchema->isObject()->shouldBeCalled()->willReturn(false); + $itemSchema->getName()->shouldBeCalled()->willReturn(null); + $itemSchema->getPath()->shouldBeCalled()->willReturn(null); + + $param->getItemSchema()->shouldBeCalled()->willReturn($itemSchema); + + $userValues = ['1', '2', '3']; + + $expected = ['fooBar' => $userValues]; + + $actual = $this->serializer->stockJson($param->reveal(), $userValues, []); + + $this->assertEquals($expected, $actual); + } + + public function test_it_serializes_objects() + { + $prop = $this->prophesize(Parameter::class); + $prop->isArray()->shouldBeCalled()->willReturn(false); + $prop->isObject()->shouldBeCalled()->willReturn(false); + $prop->getName()->shouldBeCalled()->willReturn('foo'); + $prop->getPath()->shouldBeCalled()->willReturn(null); + + $param = $this->prophesize(Parameter::class); + $param->isArray()->shouldBeCalled()->willReturn(false); + $param->isObject()->shouldBeCalled()->willReturn(true); + $param->getName()->shouldBeCalled()->willReturn('topLevel'); + $param->getPath()->shouldBeCalled()->willReturn(false); + $param->getProperty('foo')->shouldBeCalled()->willReturn($prop); + + $expected = ['topLevel' => ['foo' => true]]; + + $json = $this->serializer->stockJson($param->reveal(), ['foo' => true], []); + + $this->assertEquals($expected, $json); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Transport/MiddlewareTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/MiddlewareTest.php new file mode 100644 index 0000000..c445783 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/MiddlewareTest.php @@ -0,0 +1,59 @@ +assertEquals('pending', $promise->getState()); + + $promise->wait(); + $this->assertEquals('rejected', $promise->getState()); + } + + public function test_responses_are_left_alone_when_status_under_400() + { + $middleware = Middleware::httpErrors(); + + $response = new Response(204); + $handler = new MockHandler([$response]); + $fn = $middleware($handler); + + $promise = $fn(new Request('GET', 'http://foo.com'), []); + + $promise->then(function ($val) use ($response) { + $this->assertEquals($val, $response); + }); + + $promise->wait(); + } + + public function test_auth_handler_is_returned() + { + $generator = function () {}; + + $middleware = Middleware::authHandler($generator); + + $handler = new MockHandler([new Response(204)]); + $fn = $middleware($handler); + + $this->assertInstanceOf(AuthHandler::class, $fn); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Transport/RequestSerializerTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/RequestSerializerTest.php new file mode 100644 index 0000000..cf06220 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/RequestSerializerTest.php @@ -0,0 +1,112 @@ +js = $this->prophesize(JsonSerializer::class); + + $this->rs = new RequestSerializer($this->js->reveal()); + } + + public function test_it_ignores_undefined_params() + { + $op = $this->prophesize(Operation::class); + $op->getParam('foo')->shouldBeCalled()->willReturn(null); + + $this->assertEquals(['headers' => []], $this->rs->serializeOptions($op->reveal(), ['foo' => 'bar'])); + } + + public function test_it_serializes_queries() + { + $sch = $this->prophesize(Parameter::class); + $sch->getName()->shouldBeCalled()->willReturn('fooAlias'); + $sch->getLocation()->shouldBeCalled()->willReturn('query'); + + $op = $this->prophesize(Operation::class); + $op->getParam('foo')->shouldBeCalled()->willReturn($sch); + + $actual = $this->rs->serializeOptions($op->reveal(), ['foo' => 'bar']); + $expected = ['query' => ['fooAlias' => 'bar'], 'headers' => []]; + + $this->assertEquals($expected, $actual); + } + + public function test_it_serializes_headers() + { + $sch = $this->prophesize(Parameter::class); + $sch->getLocation()->shouldBeCalled()->willReturn('header'); + $sch->getName()->shouldBeCalled()->willReturn('fooAlias'); + $sch->getPrefixedName()->shouldBeCalled()->willReturn('prefix-fooAlias'); + + $op = $this->prophesize(Operation::class); + $op->getParam('foo')->shouldBeCalled()->willReturn($sch); + + $actual = $this->rs->serializeOptions($op->reveal(), ['foo' => 'bar']); + $expected = ['headers' => ['prefix-fooAlias' => 'bar']]; + + $this->assertEquals($expected, $actual); + } + + public function test_it_serializes_metadata_headers() + { + $itemSch = $this->prophesize(Parameter::class); + $itemSch->getName()->shouldBeCalled()->willReturn('foo'); + $itemSch->getPrefixedName()->shouldBeCalled()->willReturn('prefix-foo'); + + $sch = $this->prophesize(Parameter::class); + $sch->getItemSchema()->shouldBeCalled()->willReturn($itemSch); + $sch->getLocation()->shouldBeCalled()->willReturn('header'); + $sch->getName()->shouldBeCalled()->willReturn('metadata'); + + $op = $this->prophesize(Operation::class); + $op->getParam('metadata')->shouldBeCalled()->willReturn($sch); + + $actual = $this->rs->serializeOptions($op->reveal(), ['metadata' => ['foo' => 'bar']]); + $expected = ['headers' => ['prefix-foo' => 'bar']]; + + $this->assertEquals($expected, $actual); + } + + public function test_it_serializes_json() + { + $sch = $this->prophesize(Parameter::class); + $sch->getLocation()->shouldBeCalled()->willReturn('json'); + + $op = $this->prophesize(Operation::class); + $op->getParam('foo')->shouldBeCalled()->willReturn($sch); + $op->getJsonKey()->shouldBeCalled()->willReturn('jsonKey'); + + $this->js->stockJson($sch, 'bar', [])->shouldBeCalled()->willReturn(['foo' => 'bar']); + + $actual = $this->rs->serializeOptions($op->reveal(), ['foo' => 'bar']); + $expected = ['json' => ['jsonKey' => ['foo' => 'bar']], 'headers' => []]; + + $this->assertEquals($expected, $actual); + } + + public function test_it_serializes_raw_vals() + { + $sch = $this->prophesize(Parameter::class); + $sch->getLocation()->shouldBeCalled()->willReturn('raw'); + + $op = $this->prophesize(Operation::class); + $op->getParam('foo')->shouldBeCalled()->willReturn($sch); + + $actual = $this->rs->serializeOptions($op->reveal(), ['foo' => 'bar']); + $expected = ['body' => 'bar', 'headers' => []]; + + $this->assertEquals($expected, $actual); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Common/Transport/UtilsTest.php b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/UtilsTest.php new file mode 100644 index 0000000..3103ad4 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Common/Transport/UtilsTest.php @@ -0,0 +1,30 @@ +assertInstanceOf(Uri::class, $uri); + $this->assertEquals(uri_for('http://openstack.org/foo/bar/baz/1/2'), $uri); + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Fixtures/ComputeV2Api.php b/server/vendor/php-opencloud/common/tests/unit/Fixtures/ComputeV2Api.php new file mode 100644 index 0000000..d395d10 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Fixtures/ComputeV2Api.php @@ -0,0 +1,107 @@ + 'string', 'required' => true, 'location' => 'url']; + + public function getImage() + { + return [ + 'method' => 'GET', + 'path' => 'images/{id}', + 'params' => [self::$idParam] + ]; + } + + public function postServer() + { + return [ + 'path' => 'servers', + 'method' => 'POST', + 'jsonKey' => 'server', + 'params' => [ + 'removeMetadata' => [ + 'type' => 'object', + 'properties' => ['type' => 'string'], + ], + 'securityGroups' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'name' => ['type' => 'string'] + ] + ], + 'sentAs' => 'security_groups', + ], + 'userData' => ['type' => 'string', 'sentAs' => 'user_data'], + 'availabilityZone' => ['type' => 'string', 'sentAs' => 'availability_zone'], + 'imageId' => ['type' => 'string', 'required' => true, 'sentAs' => 'imageRef'], + 'flavorId' => ['type' => 'string', 'required' => true, 'sentAs' => 'flavorRef'], + 'networks' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'uuid' => ['type' => 'string'], + 'port' => ['type' => 'string'], + ] + ] + ], + 'name' => ['type' => 'string', 'required' => true], + 'metadata' => ['type' => 'object', 'location' => 'json'], + 'personality' => ['type' => 'string'], + 'blockDeviceMapping' => [ + 'type' => 'array', + 'sentAs' => 'block_device_mapping_v2', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'configDrive' => ['type' => 'string', 'sentAs' => 'config_drive'], + 'bootIndex' => ['type' => 'string', 'sentAs' => 'boot_index'], + 'deleteOnTermination' => ['type' => 'boolean', 'sentAs' => 'delete_on_termination'], + 'guestFormat' => ['type' => 'string', 'sentAs' => 'guest_format'], + 'destinationType' => ['type' => 'string', 'sentAs' => 'destination_type'], + 'sourceType' => ['type' => 'string', 'sentAs' => 'source_type'], + 'deviceName' => ['type' => 'string', 'sentAs' => 'device_name'], + ] + ], + ], + ] + ]; + } + + public function test() + { + return [ + 'method' => 'GET', + 'path' => 'foo', + 'params' => [ + 'id' => ['type' => 'string', 'location' => 'json'], + 'bar' => ['type' => 'string', 'location' => 'json'], + ] + ]; + } + + public function getServers() + { + return [ + 'method' => 'GET', + 'path' => 'servers', + 'params' => [ + 'changesSince' => ['sentAs' => 'changes-since', 'type' => 'string', 'location' => 'query'], + 'imageId' => ['sentAs' => 'image', 'type' => 'string', 'location' => 'query'], + 'flavorId' => ['sentAs' => 'flavor', 'type' => 'string', 'location' => 'query'], + 'name' => ['type' => 'string', 'location' => 'query'], + 'marker' => ['type' => 'string', 'location' => 'query'], + 'limit' => ['type' => 'integer', 'location' => 'query'], + 'status' => ['type' => 'string', 'location' => 'query'], + 'host' => ['type' => 'string', 'location' => 'query'] + ], + ]; + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Fixtures/IdentityV2Api.php b/server/vendor/php-opencloud/common/tests/unit/Fixtures/IdentityV2Api.php new file mode 100644 index 0000000..4cc66b5 --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Fixtures/IdentityV2Api.php @@ -0,0 +1,36 @@ + 'POST', + 'path' => 'tokens', + 'params' => [ + 'username' => [ + 'type' => 'string', + 'required' => true, + 'path' => 'auth.passwordCredentials' + ], + 'password' => [ + 'type' => 'string', + 'required' => true, + 'path' => 'auth.passwordCredentials' + ], + 'tenantId' => [ + 'type' => 'string', + 'path' => 'auth', + ], + 'tenantName' => [ + 'type' => 'string', + 'path' => 'auth', + ] + ], + ]; + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/Fixtures/IdentityV3Api.php b/server/vendor/php-opencloud/common/tests/unit/Fixtures/IdentityV3Api.php new file mode 100644 index 0000000..2fc922f --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/Fixtures/IdentityV3Api.php @@ -0,0 +1,75 @@ + 'object', + 'params' => [ + 'id' => ['type' => 'string'], + 'name' => ['type' => 'string'] + ] + ]; + } + + private function projectParam() + { + return [ + 'type' => 'object', + 'params' => [ + 'id' => ['type' => 'string'], + 'name' => ['type' => 'string'], + 'domain' => $this->domainParam(), + ] + ]; + } + + public function postTokens() + { + return [ + 'method' => 'POST', + 'path' => 'tokens', + 'params' => [ + 'methods' => [ + 'type' => 'array', + 'path' => 'auth.identity', + 'items' => [ + 'type' => 'string' + ] + ], + 'user' => [ + 'path' => 'auth.identity.password', + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'type' => 'string', + ], + 'name' => [ + 'type' => 'string', + ], + 'password' => [ + 'type' => 'string', + ], + 'domain' => $this->domainParam() + ] + ], + 'tokenId' => [ + 'type' => 'string', + 'path' => 'auth.identity.token', + 'sentAs' => 'id', + ], + 'scope' => [ + 'type' => 'object', + 'path' => 'auth', + 'properties' => [ + 'project' => $this->projectParam(), + 'domain' => $this->domainParam() + ] + ] + ] + ]; + } +} diff --git a/server/vendor/php-opencloud/common/tests/unit/TestCase.php b/server/vendor/php-opencloud/common/tests/unit/TestCase.php new file mode 100644 index 0000000..610d88a --- /dev/null +++ b/server/vendor/php-opencloud/common/tests/unit/TestCase.php @@ -0,0 +1,99 @@ +client = $this->prophesize(ClientInterface::class); + } + + protected function createResponse($status, array $headers, array $json) + { + return new Response($status, $headers, stream_for(json_encode($json))); + } + + protected function getFixture($file) + { + if (!$this->rootFixturesDir) { + throw new \RuntimeException('Root fixtures dir not set'); + } + + $path = $this->rootFixturesDir . '/Fixtures/' . $file . '.resp'; + + if (!file_exists($path)) { + throw new \RuntimeException(sprintf("%s does not exist", $path)); + } + + return parse_response(file_get_contents($path)); + } + + protected function setupMock($method, $path, $body = null, array $headers = [], $response) + { + $options = ['headers' => $headers]; + + if (!empty($body)) { + $options[is_array($body) ? 'json' : 'body'] = $body; + } + + if (is_string($response)) { + $response = $this->getFixture($response); + } + + $this->client + ->request($method, $path, $options) + ->shouldBeCalled() + ->willReturn($response); + } + + protected function createFn($receiver, $method, $args) + { + return function () use ($receiver, $method, $args) { + return $receiver->$method($args); + }; + } + + protected function listTest(callable $call, $urlPath, $modelName = null, $responseFile = null) + { + $modelName = $modelName ?: $urlPath; + $responseFile = $responseFile ?: $urlPath; + + $this->setupMock('GET', $urlPath, null, [], $responseFile); + + $resources = call_user_func($call); + + $this->assertInstanceOf('\Generator', $resources); + + $count = 0; + + foreach ($resources as $resource) { + $this->assertInstanceOf('OpenStack\Identity\v3\Models\\' . ucfirst($modelName), $resource); + ++$count; + } + + $this->assertEquals(2, $count); + } + + protected function getTest(callable $call, $modelName) + { + $resource = call_user_func($call); + + $this->assertInstanceOf('OpenStack\Identity\v3\Models\\' . ucfirst($modelName), $resource); + $this->assertEquals('id', $resource->id); + } +}