| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483 |
- <?php
- namespace Codeception\Module;
- use Codeception\Exception\ConfigurationException;
- use Codeception\Exception\ModuleException;
- use Codeception\Lib\Interfaces\ConflictsWithModule;
- use Codeception\Module as CodeceptionModule;
- use Codeception\PHPUnit\Constraint\JsonContains;
- use Codeception\PHPUnit\Constraint\JsonType as JsonTypeConstraint;
- use Codeception\TestInterface;
- use Codeception\Lib\Interfaces\API;
- use Codeception\Lib\Framework;
- use Codeception\Lib\InnerBrowser;
- use Codeception\Lib\Interfaces\DependsOnModule;
- use Codeception\Lib\Interfaces\PartedModule;
- use Codeception\Util\JsonArray;
- use Codeception\Util\JsonType;
- use Codeception\Util\XmlStructure;
- use Codeception\Util\Soap as XmlUtils;
- /**
- * Module for testing REST WebService.
- *
- * This module can be used either with frameworks or PHPBrowser.
- * If a framework module is connected, the testing will occur in the application directly.
- * Otherwise, a PHPBrowser should be specified as a dependency to send requests and receive responses from a server.
- *
- * ## Configuration
- *
- * * url *optional* - the url of api
- *
- * This module requires PHPBrowser or any of Framework modules enabled.
- *
- * ### Example
- *
- * modules:
- * enabled:
- * - REST:
- * depends: PhpBrowser
- * url: 'http://serviceapp/api/v1/'
- *
- * ## Public Properties
- *
- * * headers - array of headers going to be sent.
- * * params - array of sent data
- * * response - last response (string)
- *
- * ## Parts
- *
- * * Json - actions for validating Json responses (no Xml responses)
- * * Xml - actions for validating XML responses (no Json responses)
- *
- * ## Conflicts
- *
- * Conflicts with SOAP module
- *
- */
- class REST extends CodeceptionModule implements DependsOnModule, PartedModule, API, ConflictsWithModule
- {
- protected $config = [
- 'url' => '',
- 'aws' => ''
- ];
- protected $dependencyMessage = <<<EOF
- Example configuring PhpBrowser as backend for REST module.
- --
- modules:
- enabled:
- - REST:
- depends: PhpBrowser
- url: http://localhost/api/
- --
- Framework modules can be used for testing of API as well.
- EOF;
- /**
- * @var \Symfony\Component\HttpKernel\Client|\Symfony\Component\BrowserKit\Client
- */
- public $client = null;
- public $isFunctional = false;
- /**
- * @var InnerBrowser
- */
- protected $connectionModule;
- public $params = [];
- public $response = "";
- public function _before(TestInterface $test)
- {
- $this->client = &$this->connectionModule->client;
- $this->resetVariables();
- }
- protected function resetVariables()
- {
- $this->params = [];
- $this->response = "";
- $this->connectionModule->headers = [];
- }
- public function _conflicts()
- {
- return 'Codeception\Lib\Interfaces\API';
- }
- public function _depends()
- {
- return ['Codeception\Lib\InnerBrowser' => $this->dependencyMessage];
- }
- public function _parts()
- {
- return ['xml', 'json'];
- }
- public function _inject(InnerBrowser $connection)
- {
- $this->connectionModule = $connection;
- if ($this->connectionModule instanceof Framework) {
- $this->isFunctional = true;
- }
- if ($this->connectionModule instanceof PhpBrowser) {
- if (!$this->connectionModule->_getConfig('url')) {
- $this->connectionModule->_setConfig(['url' => $this->config['url']]);
- }
- }
- }
- protected function getRunningClient()
- {
- if ($this->client->getInternalRequest() === null) {
- throw new ModuleException($this, "Response is empty. Use `\$I->sendXXX()` methods to send HTTP request");
- }
- return $this->client;
- }
- /**
- * Sets HTTP header valid for all next requests. Use `deleteHeader` to unset it
- *
- * ```php
- * <?php
- * $I->haveHttpHeader('Content-Type', 'application/json');
- * // all next requests will contain this header
- * ?>
- * ```
- *
- * @param $name
- * @param $value
- * @part json
- * @part xml
- */
- public function haveHttpHeader($name, $value)
- {
- $this->connectionModule->haveHttpHeader($name, $value);
- }
- /**
- * Deletes the header with the passed name. Subsequent requests
- * will not have the deleted header in its request.
- *
- * Example:
- * ```php
- * <?php
- * $I->haveHttpHeader('X-Requested-With', 'Codeception');
- * $I->sendGET('test-headers.php');
- * // ...
- * $I->deleteHeader('X-Requested-With');
- * $I->sendPOST('some-other-page.php');
- * ?>
- * ```
- *
- * @param string $name the name of the header to delete.
- * @part json
- * @part xml
- */
- public function deleteHeader($name)
- {
- $this->connectionModule->deleteHeader($name);
- }
- /**
- * Checks over the given HTTP header and (optionally)
- * its value, asserting that are there
- *
- * @param $name
- * @param $value
- * @part json
- * @part xml
- */
- public function seeHttpHeader($name, $value = null)
- {
- if ($value !== null) {
- $this->assertEquals(
- $value,
- $this->getRunningClient()->getInternalResponse()->getHeader($name)
- );
- return;
- }
- $this->assertNotNull($this->getRunningClient()->getInternalResponse()->getHeader($name));
- }
- /**
- * Checks over the given HTTP header and (optionally)
- * its value, asserting that are not there
- *
- * @param $name
- * @param $value
- * @part json
- * @part xml
- */
- public function dontSeeHttpHeader($name, $value = null)
- {
- if ($value !== null) {
- $this->assertNotEquals(
- $value,
- $this->getRunningClient()->getInternalResponse()->getHeader($name)
- );
- return;
- }
- $this->assertNull($this->getRunningClient()->getInternalResponse()->getHeader($name));
- }
- /**
- * Checks that http response header is received only once.
- * HTTP RFC2616 allows multiple response headers with the same name.
- * You can check that you didn't accidentally sent the same header twice.
- *
- * ``` php
- * <?php
- * $I->seeHttpHeaderOnce('Cache-Control');
- * ?>>
- * ```
- *
- * @param $name
- * @part json
- * @part xml
- */
- public function seeHttpHeaderOnce($name)
- {
- $headers = $this->getRunningClient()->getInternalResponse()->getHeader($name, false);
- $this->assertCount(1, $headers);
- }
- /**
- * Returns the value of the specified header name
- *
- * @param $name
- * @param Boolean $first Whether to return the first value or all header values
- *
- * @return string|array The first header value if $first is true, an array of values otherwise
- * @part json
- * @part xml
- */
- public function grabHttpHeader($name, $first = true)
- {
- return $this->getRunningClient()->getInternalResponse()->getHeader($name, $first);
- }
- /**
- * Adds HTTP authentication via username/password.
- *
- * @param $username
- * @param $password
- * @part json
- * @part xml
- */
- public function amHttpAuthenticated($username, $password)
- {
- if ($this->isFunctional) {
- $this->client->setServerParameter('PHP_AUTH_USER', $username);
- $this->client->setServerParameter('PHP_AUTH_PW', $password);
- } else {
- $this->client->setAuth($username, $password);
- }
- }
- /**
- * Adds Digest authentication via username/password.
- *
- * @param $username
- * @param $password
- * @part json
- * @part xml
- */
- public function amDigestAuthenticated($username, $password)
- {
- if ($this->isFunctional) {
- throw new ModuleException(__METHOD__, 'Not supported by functional modules');
- }
- $this->client->setAuth($username, $password, 'digest');
- }
- /**
- * Adds Bearer authentication via access token.
- *
- * @param $accessToken
- * @part json
- * @part xml
- */
- public function amBearerAuthenticated($accessToken)
- {
- $this->haveHttpHeader('Authorization', 'Bearer ' . $accessToken);
- }
- /**
- * Adds NTLM authentication via username/password.
- * Requires client to be Guzzle >=6.3.0
- * Out of scope for functional modules.
- *
- * Example:
- * ```php
- * <?php
- * $I->amNTLMAuthenticated('jon_snow', 'targaryen');
- * ?>
- * ```
- *
- * @param $username
- * @param $password
- * @throws ModuleException
- * @part json
- * @part xml
- */
- public function amNTLMAuthenticated($username, $password)
- {
- if ($this->isFunctional) {
- throw new ModuleException(__METHOD__, 'Not supported by functional modules');
- }
- if (!defined('\GuzzleHttp\Client::VERSION')) {
- throw new ModuleException(__METHOD__, 'Not supported if not using a Guzzle client');
- }
- if (version_compare(\GuzzleHttp\Client::VERSION, '6.2.1', 'lt')) {
- throw new ModuleException(__METHOD__, 'Guzzle '.\GuzzleHttp\Client::VERSION.' found. Requires Guzzle >=6.3.0 for NTLM auth option');
- }
- $this->client->setAuth($username, $password, 'ntlm');
- }
- /**
- * Allows to send REST request using AWS Authorization
- *
- * Only works with PhpBrowser
- * Example Config:
- * ```yml
- * modules:
- * enabled:
- * - REST:
- * aws:
- * key: accessKey
- * secret: accessSecret
- * service: awsService
- * region: awsRegion
- * ```
- * Code:
- * ```php
- * <?php
- * $I->amAWSAuthenticated();
- * ?>
- * ```
- * @param array $additionalAWSConfig
- * @throws ModuleException
- */
- public function amAWSAuthenticated($additionalAWSConfig = [])
- {
- if (method_exists($this->client, 'setAwsAuth')) {
- $config = array_merge($this->config['aws'], $additionalAWSConfig);
- if (!isset($config['key'])) {
- throw new ConfigurationException('AWS Key is not set');
- }
- if (!isset($config['secret'])) {
- throw new ConfigurationException('AWS Secret is not set');
- }
- if (!isset($config['service'])) {
- throw new ConfigurationException('AWS Service is not set');
- }
- if (!isset($config['region'])) {
- throw new ConfigurationException('AWS Region is not set');
- }
- $this->client->setAwsAuth($config);
- }
- }
- /**
- * Sends a POST request to given uri. Parameters and files can be provided separately.
- *
- * Example:
- * ```php
- * <?php
- * //simple POST call
- * $I->sendPOST('/message', ['subject' => 'Read this!', 'to' => 'johndoe@example.com']);
- * //simple upload method
- * $I->sendPOST('/message/24', ['inline' => 0], ['attachmentFile' => codecept_data_dir('sample_file.pdf')]);
- * //uploading a file with a custom name and mime-type. This is also useful to simulate upload errors.
- * $I->sendPOST('/message/24', ['inline' => 0], [
- * 'attachmentFile' => [
- * 'name' => 'document.pdf',
- * 'type' => 'application/pdf',
- * 'error' => UPLOAD_ERR_OK,
- * 'size' => filesize(codecept_data_dir('sample_file.pdf')),
- * 'tmp_name' => codecept_data_dir('sample_file.pdf')
- * ]
- * ]);
- * ```
- *
- * @param $url
- * @param array|\JsonSerializable $params
- * @param array $files A list of filenames or "mocks" of $_FILES (each entry being an array with the following
- * keys: name, type, error, size, tmp_name (pointing to the real file path). Each key works
- * as the "name" attribute of a file input field.
- *
- * @see http://php.net/manual/en/features.file-upload.post-method.php
- * @see codecept_data_dir()
- * @part json
- * @part xml
- */
- public function sendPOST($url, $params = [], $files = [])
- {
- $this->execute('POST', $url, $params, $files);
- }
- /**
- * Sends a HEAD request to given uri.
- *
- * @param $url
- * @param array $params
- * @part json
- * @part xml
- */
- public function sendHEAD($url, $params = [])
- {
- $this->execute('HEAD', $url, $params);
- }
- /**
- * Sends an OPTIONS request to given uri.
- *
- * @param $url
- * @param array $params
- * @part json
- * @part xml
- */
- public function sendOPTIONS($url, $params = [])
- {
- $this->execute('OPTIONS', $url, $params);
- }
- /**
- * Sends a GET request to given uri.
- *
- * @param $url
- * @param array $params
- * @part json
- * @part xml
- */
- public function sendGET($url, $params = [])
- {
- $this->execute('GET', $url, $params);
- }
- /**
- * Sends PUT request to given uri.
- *
- * @param $url
- * @param array $params
- * @param array $files
- * @part json
- * @part xml
- */
- public function sendPUT($url, $params = [], $files = [])
- {
- $this->execute('PUT', $url, $params, $files);
- }
- /**
- * Sends PATCH request to given uri.
- *
- * @param $url
- * @param array $params
- * @param array $files
- * @part json
- * @part xml
- */
- public function sendPATCH($url, $params = [], $files = [])
- {
- $this->execute('PATCH', $url, $params, $files);
- }
- /**
- * Sends DELETE request to given uri.
- *
- * @param $url
- * @param array $params
- * @param array $files
- * @part json
- * @part xml
- */
- public function sendDELETE($url, $params = [], $files = [])
- {
- $this->execute('DELETE', $url, $params, $files);
- }
- /**
- * Sets Headers "Link" as one header "Link" based on linkEntries
- *
- * @param array $linkEntries (entry is array with keys "uri" and "link-param")
- *
- * @link http://tools.ietf.org/html/rfc2068#section-19.6.2.4
- *
- * @author samva.ua@gmail.com
- */
- private function setHeaderLink(array $linkEntries)
- {
- $values = [];
- foreach ($linkEntries as $linkEntry) {
- \PHPUnit\Framework\Assert::assertArrayHasKey(
- 'uri',
- $linkEntry,
- 'linkEntry should contain property "uri"'
- );
- \PHPUnit\Framework\Assert::assertArrayHasKey(
- 'link-param',
- $linkEntry,
- 'linkEntry should contain property "link-param"'
- );
- $values[] = $linkEntry['uri'] . '; ' . $linkEntry['link-param'];
- }
- $this->haveHttpHeader('Link', implode(', ', $values));
- }
- /**
- * Sends LINK request to given uri.
- *
- * @param $url
- * @param array $linkEntries (entry is array with keys "uri" and "link-param")
- *
- * @link http://tools.ietf.org/html/rfc2068#section-19.6.2.4
- *
- * @author samva.ua@gmail.com
- * @part json
- * @part xml
- */
- public function sendLINK($url, array $linkEntries)
- {
- $this->setHeaderLink($linkEntries);
- $this->execute('LINK', $url);
- }
- /**
- * Sends UNLINK request to given uri.
- *
- * @param $url
- * @param array $linkEntries (entry is array with keys "uri" and "link-param")
- * @link http://tools.ietf.org/html/rfc2068#section-19.6.2.4
- * @author samva.ua@gmail.com
- * @part json
- * @part xml
- */
- public function sendUNLINK($url, array $linkEntries)
- {
- $this->setHeaderLink($linkEntries);
- $this->execute('UNLINK', $url);
- }
- protected function execute($method, $url, $parameters = [], $files = [])
- {
- // allow full url to be requested
- if (strpos($url, '://') === false) {
- $url = $this->config['url'] . $url;
- if ($this->config['url'] && strpos($url, '://') === false && $this->config['url'][0] !== '/') {
- $url = '/' . $url;
- }
- }
- $this->params = $parameters;
- $parameters = $this->encodeApplicationJson($method, $parameters);
- if (is_array($parameters) || $method === 'GET') {
- if (!empty($parameters) && $method === 'GET') {
- if (strpos($url, '?') !== false) {
- $url .= '&';
- } else {
- $url .= '?';
- }
- $url .= http_build_query($parameters);
- }
- if ($method == 'GET') {
- $this->debugSection("Request", "$method $url");
- $files = [];
- } else {
- $this->debugSection("Request", "$method $url " . json_encode($parameters));
- $files = $this->formatFilesArray($files);
- }
- $this->response = (string)$this->connectionModule->_request($method, $url, $parameters, $files);
- } else {
- $requestData = $parameters;
- if ($this->isBinaryData($requestData)) {
- $requestData = $this->binaryToDebugString($requestData);
- }
- $this->debugSection("Request", "$method $url " . $requestData);
- $this->response = (string)$this->connectionModule->_request($method, $url, [], $files, [], $parameters);
- }
- $printedResponse = $this->response;
- if ($this->isBinaryData($printedResponse)) {
- $printedResponse = $this->binaryToDebugString($printedResponse);
- }
- $this->debugSection("Response", $printedResponse);
- }
- /**
- * Check if data has non-printable bytes and it is not a valid unicode string
- *
- * @param string $data the text or binary data string
- * @return boolean
- */
- protected function isBinaryData($data)
- {
- return !ctype_print($data) && false === mb_detect_encoding($data, mb_detect_order(), true);
- }
- /**
- * Format a binary string for debug printing
- *
- * @param string $data the binary data string
- * @return string the debug string
- */
- protected function binaryToDebugString($data)
- {
- return '[binary-data length:' . strlen($data) . ' md5:' . md5($data) . ']';
- }
- protected function encodeApplicationJson($method, $parameters)
- {
- if ($method !== 'GET' && array_key_exists('Content-Type', $this->connectionModule->headers)
- && ($this->connectionModule->headers['Content-Type'] === 'application/json'
- || preg_match('!^application/.+\+json$!', $this->connectionModule->headers['Content-Type'])
- )
- ) {
- if ($parameters instanceof \JsonSerializable) {
- return json_encode($parameters);
- }
- if (is_array($parameters) || $parameters instanceof \ArrayAccess) {
- $parameters = $this->scalarizeArray($parameters);
- return json_encode($parameters);
- }
- }
- return $parameters;
- }
- private function formatFilesArray(array $files)
- {
- foreach ($files as $name => $value) {
- if (is_string($value)) {
- $this->checkFileBeforeUpload($value);
- $files[$name] = [
- 'name' => basename($value),
- 'tmp_name' => $value,
- 'size' => filesize($value),
- 'type' => $this->getFileType($value),
- 'error' => 0,
- ];
- continue;
- } elseif (is_array($value)) {
- if (isset($value['tmp_name'])) {
- $this->checkFileBeforeUpload($value['tmp_name']);
- if (!isset($value['name'])) {
- $value['name'] = basename($value['tmp_name']);
- }
- if (!isset($value['size'])) {
- $value['size'] = filesize($value['tmp_name']);
- }
- if (!isset($value['type'])) {
- $value['type'] = $this->getFileType($value['tmp_name']);
- }
- if (!isset($value['error'])) {
- $value['error'] = 0;
- }
- } else {
- $files[$name] = $this->formatFilesArray($value);
- }
- } elseif (is_object($value)) {
- /**
- * do nothing, probably the user knows what he is doing
- * @issue https://github.com/Codeception/Codeception/issues/3298
- */
- } else {
- throw new ModuleException(__CLASS__, "Invalid value of key $name in files array");
- }
- }
- return $files;
- }
- private function getFileType($file)
- {
- if (function_exists('mime_content_type') && mime_content_type($file)) {
- return mime_content_type($file);
- }
- return 'application/octet-stream';
- }
- private function checkFileBeforeUpload($file)
- {
- if (!file_exists($file)) {
- throw new ModuleException(__CLASS__, "File $file does not exist");
- }
- if (!is_readable($file)) {
- throw new ModuleException(__CLASS__, "File $file is not readable");
- }
- if (!is_file($file)) {
- throw new ModuleException(__CLASS__, "File $file is not a regular file");
- }
- }
- /**
- * Checks whether last response was valid JSON.
- * This is done with json_last_error function.
- *
- * @part json
- */
- public function seeResponseIsJson()
- {
- $responseContent = $this->connectionModule->_getResponseContent();
- \PHPUnit\Framework\Assert::assertNotEquals('', $responseContent, 'response is empty');
- json_decode($responseContent);
- $errorCode = json_last_error();
- $errorMessage = json_last_error_msg();
- \PHPUnit\Framework\Assert::assertEquals(
- JSON_ERROR_NONE,
- $errorCode,
- sprintf(
- "Invalid json: %s. System message: %s.",
- $responseContent,
- $errorMessage
- )
- );
- }
- /**
- * Checks whether the last response contains text.
- *
- * @param $text
- * @part json
- * @part xml
- */
- public function seeResponseContains($text)
- {
- $this->assertContains($text, $this->connectionModule->_getResponseContent(), "REST response contains");
- }
- /**
- * Checks whether last response do not contain text.
- *
- * @param $text
- * @part json
- * @part xml
- */
- public function dontSeeResponseContains($text)
- {
- $this->assertNotContains($text, $this->connectionModule->_getResponseContent(), "REST response contains");
- }
- /**
- * Checks whether the last JSON response contains provided array.
- * The response is converted to array with json_decode($response, true)
- * Thus, JSON is represented by associative array.
- * This method matches that response array contains provided array.
- *
- * Examples:
- *
- * ``` php
- * <?php
- * // response: {name: john, email: john@gmail.com}
- * $I->seeResponseContainsJson(array('name' => 'john'));
- *
- * // response {user: john, profile: { email: john@gmail.com }}
- * $I->seeResponseContainsJson(array('email' => 'john@gmail.com'));
- *
- * ?>
- * ```
- *
- * This method recursively checks if one array can be found inside of another.
- *
- * @param array $json
- * @part json
- */
- public function seeResponseContainsJson($json = [])
- {
- \PHPUnit\Framework\Assert::assertThat(
- $this->connectionModule->_getResponseContent(),
- new JsonContains($json)
- );
- }
- /**
- * Returns current response so that it can be used in next scenario steps.
- *
- * Example:
- *
- * ``` php
- * <?php
- * $user_id = $I->grabResponse();
- * $I->sendPUT('/user', array('id' => $user_id, 'name' => 'davert'));
- * ?>
- * ```
- *
- * @version 1.1
- * @return string
- * @part json
- * @part xml
- */
- public function grabResponse()
- {
- return $this->connectionModule->_getResponseContent();
- }
- /**
- * Returns data from the current JSON response using [JSONPath](http://goessner.net/articles/JsonPath/) as selector.
- * JsonPath is XPath equivalent for querying Json structures.
- * Try your JsonPath expressions [online](http://jsonpath.curiousconcept.com/).
- * Even for a single value an array is returned.
- *
- * This method **require [`flow/jsonpath` > 0.2](https://github.com/FlowCommunications/JSONPath/) library to be installed**.
- *
- * Example:
- *
- * ``` php
- * <?php
- * // match the first `user.id` in json
- * $firstUserId = $I->grabDataFromResponseByJsonPath('$..users[0].id');
- * $I->sendPUT('/user', array('id' => $firstUserId[0], 'name' => 'davert'));
- * ?>
- * ```
- *
- * @param string $jsonPath
- * @return array Array of matching items
- * @version 2.0.9
- * @throws \Exception
- * @part json
- */
- public function grabDataFromResponseByJsonPath($jsonPath)
- {
- return (new JsonArray($this->connectionModule->_getResponseContent()))->filterByJsonPath($jsonPath);
- }
- /**
- * Checks if json structure in response matches the xpath provided.
- * JSON is not supposed to be checked against XPath, yet it can be converted to xml and used with XPath.
- * This assertion allows you to check the structure of response json.
- * *
- * ```json
- * { "store": {
- * "book": [
- * { "category": "reference",
- * "author": "Nigel Rees",
- * "title": "Sayings of the Century",
- * "price": 8.95
- * },
- * { "category": "fiction",
- * "author": "Evelyn Waugh",
- * "title": "Sword of Honour",
- * "price": 12.99
- * }
- * ],
- * "bicycle": {
- * "color": "red",
- * "price": 19.95
- * }
- * }
- * }
- * ```
- *
- * ```php
- * <?php
- * // at least one book in store has author
- * $I->seeResponseJsonMatchesXpath('//store/book/author');
- * // first book in store has author
- * $I->seeResponseJsonMatchesXpath('//store/book[1]/author');
- * // at least one item in store has price
- * $I->seeResponseJsonMatchesXpath('/store//price');
- * ?>
- * ```
- * @param string $xpath
- * @part json
- * @version 2.0.9
- */
- public function seeResponseJsonMatchesXpath($xpath)
- {
- $response = $this->connectionModule->_getResponseContent();
- $this->assertGreaterThan(
- 0,
- (new JsonArray($response))->filterByXPath($xpath)->length,
- "Received JSON did not match the XPath `$xpath`.\nJson Response: \n" . $response
- );
- }
- /**
- * Opposite to seeResponseJsonMatchesXpath
- *
- * @param string $xpath
- * @part json
- */
- public function dontSeeResponseJsonMatchesXpath($xpath)
- {
- $response = $this->connectionModule->_getResponseContent();
- $this->assertEquals(
- 0,
- (new JsonArray($response))->filterByXPath($xpath)->length,
- "Received JSON matched the XPath `$xpath`.\nJson Response: \n" . $response
- );
- }
- /**
- * Checks if json structure in response matches [JsonPath](http://goessner.net/articles/JsonPath/).
- * JsonPath is XPath equivalent for querying Json structures.
- * Try your JsonPath expressions [online](http://jsonpath.curiousconcept.com/).
- * This assertion allows you to check the structure of response json.
- *
- * This method **require [`flow/jsonpath` > 0.2](https://github.com/FlowCommunications/JSONPath/) library to be installed**.
- *
- * ```json
- * { "store": {
- * "book": [
- * { "category": "reference",
- * "author": "Nigel Rees",
- * "title": "Sayings of the Century",
- * "price": 8.95
- * },
- * { "category": "fiction",
- * "author": "Evelyn Waugh",
- * "title": "Sword of Honour",
- * "price": 12.99
- * }
- * ],
- * "bicycle": {
- * "color": "red",
- * "price": 19.95
- * }
- * }
- * }
- * ```
- *
- * ```php
- * <?php
- * // at least one book in store has author
- * $I->seeResponseJsonMatchesJsonPath('$.store.book[*].author');
- * // first book in store has author
- * $I->seeResponseJsonMatchesJsonPath('$.store.book[0].author');
- * // at least one item in store has price
- * $I->seeResponseJsonMatchesJsonPath('$.store..price');
- * ?>
- * ```
- *
- * @param string $jsonPath
- * @part json
- * @version 2.0.9
- */
- public function seeResponseJsonMatchesJsonPath($jsonPath)
- {
- $response = $this->connectionModule->_getResponseContent();
- $this->assertNotEmpty(
- (new JsonArray($response))->filterByJsonPath($jsonPath),
- "Received JSON did not match the JsonPath `$jsonPath`.\nJson Response: \n" . $response
- );
- }
- /**
- * Opposite to seeResponseJsonMatchesJsonPath
- *
- * @param string $jsonPath
- * @part json
- */
- public function dontSeeResponseJsonMatchesJsonPath($jsonPath)
- {
- $response = $this->connectionModule->_getResponseContent();
- $this->assertEmpty(
- (new JsonArray($response))->filterByJsonPath($jsonPath),
- "Received JSON matched the JsonPath `$jsonPath`.\nJson Response: \n" . $response
- );
- }
- /**
- * Opposite to seeResponseContainsJson
- *
- * @part json
- * @param array $json
- */
- public function dontSeeResponseContainsJson($json = [])
- {
- $jsonResponseArray = new JsonArray($this->connectionModule->_getResponseContent());
- $this->assertFalse(
- $jsonResponseArray->containsArray($json),
- "Response JSON contains provided JSON\n"
- . "- <info>" . var_export($json, true) . "</info>\n"
- . "+ " . var_export($jsonResponseArray->toArray(), true)
- );
- }
- /**
- * Checks that Json matches provided types.
- * In case you don't know the actual values of JSON data returned you can match them by type.
- * Starts check with a root element. If JSON data is array it will check the first element of an array.
- * You can specify the path in the json which should be checked with JsonPath
- *
- * Basic example:
- *
- * ```php
- * <?php
- * // {'user_id': 1, 'name': 'davert', 'is_active': false}
- * $I->seeResponseMatchesJsonType([
- * 'user_id' => 'integer',
- * 'name' => 'string|null',
- * 'is_active' => 'boolean'
- * ]);
- *
- * // narrow down matching with JsonPath:
- * // {"users": [{ "name": "davert"}, {"id": 1}]}
- * $I->seeResponseMatchesJsonType(['name' => 'string'], '$.users[0]');
- * ?>
- * ```
- *
- * In this case you can match that record contains fields with data types you expected.
- * The list of possible data types:
- *
- * * string
- * * integer
- * * float
- * * array (json object is array as well)
- * * boolean
- *
- * You can also use nested data type structures:
- *
- * ```php
- * <?php
- * // {'user_id': 1, 'name': 'davert', 'company': {'name': 'Codegyre'}}
- * $I->seeResponseMatchesJsonType([
- * 'user_id' => 'integer|string', // multiple types
- * 'company' => ['name' => 'string']
- * ]);
- * ?>
- * ```
- *
- * You can also apply filters to check values. Filter can be applied with `:` char after the type declaration.
- *
- * Here is the list of possible filters:
- *
- * * `integer:>{val}` - checks that integer is greater than {val} (works with float and string types too).
- * * `integer:<{val}` - checks that integer is lower than {val} (works with float and string types too).
- * * `string:url` - checks that value is valid url.
- * * `string:date` - checks that value is date in JavaScript format: https://weblog.west-wind.com/posts/2014/Jan/06/JavaScript-JSON-Date-Parsing-and-real-Dates
- * * `string:email` - checks that value is a valid email according to http://emailregex.com/
- * * `string:regex({val})` - checks that string matches a regex provided with {val}
- *
- * This is how filters can be used:
- *
- * ```php
- * <?php
- * // {'user_id': 1, 'email' => 'davert@codeception.com'}
- * $I->seeResponseMatchesJsonType([
- * 'user_id' => 'string:>0:<1000', // multiple filters can be used
- * 'email' => 'string:regex(~\@~)' // we just check that @ char is included
- * ]);
- *
- * // {'user_id': '1'}
- * $I->seeResponseMatchesJsonType([
- * 'user_id' => 'string:>0', // works with strings as well
- * }
- * ?>
- * ```
- *
- * You can also add custom filters y accessing `JsonType::addCustomFilter` method.
- * See [JsonType reference](http://codeception.com/docs/reference/JsonType).
- *
- * @part json
- * @version 2.1.3
- * @param array $jsonType
- * @param string $jsonPath
- */
- public function seeResponseMatchesJsonType(array $jsonType, $jsonPath = null)
- {
- $jsonArray = new JsonArray($this->connectionModule->_getResponseContent());
- if ($jsonPath) {
- $jsonArray = $jsonArray->filterByJsonPath($jsonPath);
- }
- \PHPUnit\Framework\Assert::assertThat($jsonArray, new JsonTypeConstraint($jsonType));
- }
- /**
- * Opposite to `seeResponseMatchesJsonType`.
- *
- * @part json
- * @see seeResponseMatchesJsonType
- * @param $jsonType jsonType structure
- * @param null $jsonPath optionally set specific path to structure with JsonPath
- * @version 2.1.3
- */
- public function dontSeeResponseMatchesJsonType($jsonType, $jsonPath = null)
- {
- $jsonArray = new JsonArray($this->connectionModule->_getResponseContent());
- if ($jsonPath) {
- $jsonArray = $jsonArray->filterByJsonPath($jsonPath);
- }
- \PHPUnit\Framework\Assert::assertThat($jsonArray, new JsonTypeConstraint($jsonType, false));
- }
- /**
- * Checks if response is exactly the same as provided.
- *
- * @part json
- * @part xml
- * @param $response
- */
- public function seeResponseEquals($expected)
- {
- $this->assertEquals($expected, $this->connectionModule->_getResponseContent());
- }
- /**
- * Checks response code equals to provided value.
- *
- * ```php
- * <?php
- * $I->seeResponseCodeIs(200);
- *
- * // preferred to use \Codeception\Util\HttpCode
- * $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
- * ```
- *
- * @part json
- * @part xml
- * @param $code
- */
- public function seeResponseCodeIs($code)
- {
- $this->connectionModule->seeResponseCodeIs($code);
- }
- /**
- * Checks that response code is not equal to provided value.
- *
- * ```php
- * <?php
- * $I->dontSeeResponseCodeIs(200);
- *
- * // preferred to use \Codeception\Util\HttpCode
- * $I->dontSeeResponseCodeIs(\Codeception\Util\HttpCode::OK);
- * ```
- *
- * @part json
- * @part xml
- * @param $code
- */
- public function dontSeeResponseCodeIs($code)
- {
- $this->connectionModule->dontSeeResponseCodeIs($code);
- }
- /**
- * Checks that the response code is 2xx
- *
- * @part json
- * @part xml
- */
- public function seeResponseCodeIsSuccessful()
- {
- $this->connectionModule->seeResponseCodeIsSuccessful();
- }
- /**
- * Checks that the response code 3xx
- *
- * @part json
- * @part xml
- */
- public function seeResponseCodeIsRedirection()
- {
- $this->connectionModule->seeResponseCodeIsRedirection();
- }
- /**
- * Checks that the response code is 4xx
- *
- * @part json
- * @part xml
- */
- public function seeResponseCodeIsClientError()
- {
- $this->connectionModule->seeResponseCodeIsClientError();
- }
- /**
- * Checks that the response code is 5xx
- *
- * @part json
- * @part xml
- */
- public function seeResponseCodeIsServerError()
- {
- $this->connectionModule->seeResponseCodeIsServerError();
- }
- /**
- * Checks whether last response was valid XML.
- * This is done with libxml_get_last_error function.
- *
- * @part xml
- */
- public function seeResponseIsXml()
- {
- libxml_use_internal_errors(true);
- $doc = simplexml_load_string($this->connectionModule->_getResponseContent());
- $num = "";
- $title = "";
- if ($doc === false) {
- $error = libxml_get_last_error();
- $num = $error->code;
- $title = trim($error->message);
- libxml_clear_errors();
- }
- libxml_use_internal_errors(false);
- \PHPUnit\Framework\Assert::assertNotSame(
- false,
- $doc,
- "xml decoding error #$num with message \"$title\", see http://www.xmlsoft.org/html/libxml-xmlerror.html"
- );
- }
- /**
- * Checks whether XML response matches XPath
- *
- * ```php
- * <?php
- * $I->seeXmlResponseMatchesXpath('//root/user[@id=1]');
- * ```
- * @part xml
- * @param $xpath
- */
- public function seeXmlResponseMatchesXpath($xpath)
- {
- $structure = new XmlStructure($this->connectionModule->_getResponseContent());
- $this->assertTrue($structure->matchesXpath($xpath), 'xpath not matched');
- }
- /**
- * Checks whether XML response does not match XPath
- *
- * ```php
- * <?php
- * $I->dontSeeXmlResponseMatchesXpath('//root/user[@id=1]');
- * ```
- * @part xml
- * @param $xpath
- */
- public function dontSeeXmlResponseMatchesXpath($xpath)
- {
- $structure = new XmlStructure($this->connectionModule->_getResponseContent());
- $this->assertFalse($structure->matchesXpath($xpath), 'accidentally matched xpath');
- }
- /**
- * Finds and returns text contents of element.
- * Element is matched by either CSS or XPath
- *
- * @param $cssOrXPath
- * @return string
- * @part xml
- */
- public function grabTextContentFromXmlElement($cssOrXPath)
- {
- $el = (new XmlStructure($this->connectionModule->_getResponseContent()))->matchElement($cssOrXPath);
- return $el->textContent;
- }
- /**
- * Finds and returns attribute of element.
- * Element is matched by either CSS or XPath
- *
- * @param $cssOrXPath
- * @param $attribute
- * @return string
- * @part xml
- */
- public function grabAttributeFromXmlElement($cssOrXPath, $attribute)
- {
- $el = (new XmlStructure($this->connectionModule->_getResponseContent()))->matchElement($cssOrXPath);
- if (!$el->hasAttribute($attribute)) {
- $this->fail("Attribute not found in element matched by '$cssOrXPath'");
- }
- return $el->getAttribute($attribute);
- }
- /**
- * Checks XML response equals provided XML.
- * Comparison is done by canonicalizing both xml`s.
- *
- * Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes).
- *
- * @param $xml
- * @part xml
- */
- public function seeXmlResponseEquals($xml)
- {
- \PHPUnit\Framework\Assert::assertXmlStringEqualsXmlString($this->connectionModule->_getResponseContent(), $xml);
- }
- /**
- * Checks XML response does not equal to provided XML.
- * Comparison is done by canonicalizing both xml`s.
- *
- * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
- *
- * @param $xml
- * @part xml
- */
- public function dontSeeXmlResponseEquals($xml)
- {
- \PHPUnit\Framework\Assert::assertXmlStringNotEqualsXmlString(
- $this->connectionModule->_getResponseContent(),
- $xml
- );
- }
- /**
- * Checks XML response includes provided XML.
- * Comparison is done by canonicalizing both xml`s.
- * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
- *
- * Example:
- *
- * ``` php
- * <?php
- * $I->seeXmlResponseIncludes("<result>1</result>");
- * ?>
- * ```
- *
- * @param $xml
- * @part xml
- */
- public function seeXmlResponseIncludes($xml)
- {
- $this->assertContains(
- XmlUtils::toXml($xml)->C14N(),
- XmlUtils::toXml($this->connectionModule->_getResponseContent())->C14N(),
- "found in XML Response"
- );
- }
- /**
- * Checks XML response does not include provided XML.
- * Comparison is done by canonicalizing both xml`s.
- * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
- *
- * @param $xml
- * @part xml
- */
- public function dontSeeXmlResponseIncludes($xml)
- {
- $this->assertNotContains(
- XmlUtils::toXml($xml)->C14N(),
- XmlUtils::toXml($this->connectionModule->_getResponseContent())->C14N(),
- "found in XML Response"
- );
- }
- /**
- * Checks if the hash of a binary response is exactly the same as provided.
- * Parameter can be passed as any hash string supported by hash(), with an
- * optional second parameter to specify the hash type, which defaults to md5.
- *
- * Example: Using md5 hash key
- *
- * ```php
- * <?php
- * $I->seeBinaryResponseEquals("8c90748342f19b195b9c6b4eff742ded");
- * ?>
- * ```
- *
- * Example: Using md5 for a file contents
- *
- * ```php
- * <?php
- * $fileData = file_get_contents("test_file.jpg");
- * $I->seeBinaryResponseEquals(md5($fileData));
- * ?>
- * ```
- * Example: Using sha256 hash
- *
- * ```php
- * <?php
- * $fileData = '/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k='; // very small jpeg
- * $I->seeBinaryResponseEquals(hash("sha256", base64_decode($fileData)), 'sha256');
- * ?>
- * ```
- *
- * @param $hash the hashed data response expected
- * @param $algo the hash algorithm to use. Default md5.
- * @part json
- * @part xml
- */
- public function seeBinaryResponseEquals($hash, $algo = 'md5')
- {
- $responseHash = hash($algo, $this->connectionModule->_getResponseContent());
- $this->assertEquals($hash, $responseHash);
- }
- /**
- * Checks if the hash of a binary response is not the same as provided.
- *
- * ```php
- * <?php
- * $I->dontSeeBinaryResponseEquals("8c90748342f19b195b9c6b4eff742ded");
- * ?>
- * ```
- * Opposite to `seeBinaryResponseEquals`
- *
- * @param $hash the hashed data response expected
- * @param $algo the hash algorithm to use. Default md5.
- * @part json
- * @part xml
- */
- public function dontSeeBinaryResponseEquals($hash, $algo = 'md5')
- {
- $responseHash = hash($algo, $this->connectionModule->_getResponseContent());
- $this->assertNotEquals($hash, $responseHash);
- }
- /**
- * Deprecated since 2.0.9 and removed since 2.1.0
- *
- * @param $path
- * @throws ModuleException
- * @deprecated
- */
- public function grabDataFromJsonResponse($path)
- {
- throw new ModuleException(
- $this,
- "This action was deprecated in Codeception 2.0.9 and removed in 2.1. "
- . "Please use `grabDataFromResponseByJsonPath` instead"
- );
- }
- /**
- * Prevents automatic redirects to be followed by the client
- *
- * ```php
- * <?php
- * $I->stopFollowingRedirects();
- * ```
- *
- * @part xml
- * @part json
- */
- public function stopFollowingRedirects()
- {
- $this->client->followRedirects(false);
- }
- /**
- * Enables automatic redirects to be followed by the client
- *
- * ```php
- * <?php
- * $I->startFollowingRedirects();
- * ```
- *
- * @part xml
- * @part json
- */
- public function startFollowingRedirects()
- {
- $this->client->followRedirects(true);
- }
- }
|