diff --git a/README.md b/README.md index bde9920..85222a7 100644 --- a/README.md +++ b/README.md @@ -274,17 +274,18 @@ When faking responses, you may occasionally wish to inspect the requests the cli The `assertSent` method accepts a callback which will be given an `CodeDredd\Soap\Client\Request` instance and should return a boolean value indicating if the request matches your expectations. In order for the test to pass, at least one request must have been issued matching the given expectations: -Right now you can only check the action - Soap::fake(); Soap::withHeaders([ 'X-First' => 'foo', ])->baseWsdl('http://test.com/v1?wsdl') - ->call('Get_Users'); + ->call('Get_Users', [ + 'name' => 'CodeDredd' + ]); Soap::assertSent(function ($request) { - return $request->action() === 'Get_Users'; + return $request->action() === 'Get_Users' && + $request->arguments() === ['name' => 'CodeDredd']; }); //Or shortcut Soap::assertActionSent('Get_Users') diff --git a/composer.json b/composer.json index cbbaedd..6825e7d 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,8 @@ "php": ">=7.2.0", "ext-soap": "*", "ext-json": "*", + "ext-dom": "*", + "ext-simplexml": "*", "illuminate/support": "^5.6 || ^6.0 || ^7.0", "phpro/soap-client": "^1.1", "php-http/guzzle6-adapter": "^2.0", diff --git a/composer.lock b/composer.lock index cf093e6..0f6b041 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "20b98089705da28bf322f6ed8b8b97ca", + "content-hash": "9257626529083d40232805d69d34add7", "packages": [ { "name": "brick/math", @@ -536,16 +536,16 @@ }, { "name": "laravel/framework", - "version": "v7.7.0", + "version": "v7.7.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "3c9a67a35a4386f861ae0a5eb178a709552e4fa9" + "reference": "4de129177f889cd7672e732d8808020adfb7b121" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/3c9a67a35a4386f861ae0a5eb178a709552e4fa9", - "reference": "3c9a67a35a4386f861ae0a5eb178a709552e4fa9", + "url": "https://api.github.com/repos/laravel/framework/zipball/4de129177f889cd7672e732d8808020adfb7b121", + "reference": "4de129177f889cd7672e732d8808020adfb7b121", "shasum": "" }, "require": { @@ -684,7 +684,7 @@ "framework", "laravel" ], - "time": "2020-04-21T13:50:23+00:00" + "time": "2020-04-21T18:57:12+00:00" }, { "name": "league/commonmark", @@ -5684,7 +5684,9 @@ "platform": { "php": ">=7.2.0", "ext-soap": "*", - "ext-json": "*" + "ext-json": "*", + "ext-dom": "*", + "ext-simplexml": "*" }, "platform-dev": [] } diff --git a/src/Client/Request.php b/src/Client/Request.php index 19f5b07..f80bbaa 100644 --- a/src/Client/Request.php +++ b/src/Client/Request.php @@ -2,9 +2,15 @@ namespace CodeDredd\Soap\Client; -use Phpro\SoapClient\Type\MultiArgumentRequest; +use CodeDredd\Soap\Xml\SoapXml; +use CodeDredd\Soap\Xml\XMLSerializer; +use Illuminate\Support\Arr; -class Request extends MultiArgumentRequest +/** + * Class Request + * @package CodeDredd\Soap\Client + */ +class Request { /** * The underlying PSR request. @@ -22,8 +28,6 @@ class Request extends MultiArgumentRequest public function __construct($request) { $this->request = $request; - //@todo still need to get the arguments some how - parent::__construct([]); } /** @@ -33,4 +37,30 @@ public function action(): string { return $this->request->getHeaderLine('SOAPAction'); } + + /** + * @return \Psr\Http\Message\RequestInterface + */ + public function getRequest() { + return $this->request; + } + + /** + * Return complete xml request body + * + * @return string + */ + public function xmlContent() { + return $this->request->getBody()->getContents(); + } + + /** + * Return request arguments + * + * @return array + */ + public function arguments(): array { + $xml = SoapXml::fromString($this->xmlContent()); + return Arr::first(XMLSerializer::domNodeToArray($xml->getBody())); + } } diff --git a/src/Client/Response.php b/src/Client/Response.php index 388a1b4..dbbdac3 100644 --- a/src/Client/Response.php +++ b/src/Client/Response.php @@ -5,7 +5,7 @@ use LogicException; use ArrayAccess; use CodeDredd\Soap\Exceptions\RequestException; -use CodeDredd\Soap\XML\SoapXml; +use CodeDredd\Soap\Xml\SoapXml; use GuzzleHttp\Psr7\Response as Psr7Response; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; diff --git a/src/Faker/EngineFaker.php b/src/Faker/EngineFaker.php index 2b34006..f7e5c7f 100644 --- a/src/Faker/EngineFaker.php +++ b/src/Faker/EngineFaker.php @@ -4,7 +4,7 @@ namespace CodeDredd\Soap\Faker; -use CodeDredd\Soap\XML\XMLSerializer; +use CodeDredd\Soap\Xml\XMLSerializer; use Phpro\SoapClient\Soap\Engine\DriverInterface; use Phpro\SoapClient\Soap\Engine\EngineInterface; use Phpro\SoapClient\Soap\Engine\Metadata\MetadataInterface; @@ -13,6 +13,10 @@ use Phpro\SoapClient\Soap\HttpBinding\SoapRequest; use Phpro\SoapClient\Xml\SoapXml; +/** + * Class EngineFaker + * @package CodeDredd\Soap\Faker + */ class EngineFaker implements EngineInterface { /** @@ -25,8 +29,17 @@ class EngineFaker implements EngineInterface */ private $handler; + /** + * @var string + */ private $wsdl; + /** + * EngineFaker constructor. + * @param DriverInterface $driver + * @param HandlerInterface $handler + * @param string $wsdl + */ public function __construct( DriverInterface $driver, HandlerInterface $handler, @@ -37,24 +50,30 @@ public function __construct( $this->wsdl = $wsdl; } + /** + * @return MetadataInterface + */ public function getMetadata(): MetadataInterface { return $this->driver->getMetadata(); } + /** + * @param string $method + * @param array $arguments + * @return mixed + */ public function request(string $method, array $arguments) { - $arguments = [ - 'SOAP-ENV:Body' => $arguments - ]; - $xml = new \SimpleXMLElement(''); - XMLSerializer::arrayToXml($arguments, $xml); - $request = new SoapRequest($xml->asXML(), $this->wsdl, $method, 1); + $request = new SoapRequest(XMLSerializer::arrayToSoapXml($arguments), $this->wsdl, $method, 1); $response = $this->handler->request($request); return json_decode($response->getResponse()); } + /** + * @return LastRequestInfo + */ public function collectLastRequestInfo(): LastRequestInfo { return $this->handler->collectLastRequestInfo(); diff --git a/src/SoapFactory.php b/src/SoapFactory.php index 8664cfd..9f2022c 100644 --- a/src/SoapFactory.php +++ b/src/SoapFactory.php @@ -5,7 +5,7 @@ use Closure; use CodeDredd\Soap\Client\Request; use CodeDredd\Soap\Client\ResponseSequence; -use CodeDredd\Soap\XML\XMLSerializer; +use CodeDredd\Soap\Xml\XMLSerializer; use GuzzleHttp\Psr7\Response as Psr7Response; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; diff --git a/src/XML/XMLSerializer.php b/src/XML/XMLSerializer.php deleted file mode 100644 index c9bb6a3..0000000 --- a/src/XML/XMLSerializer.php +++ /dev/null @@ -1,124 +0,0 @@ - - * Date: 16.04.2020 - * Time: 16:12 - */ -class XMLSerializer { - - /** - * - * The most advanced method of serialization. - * - * @param mixed $obj => can be an objectm, an array or string. may contain unlimited number of subobjects and subarrays - * @param string $wrapper => main wrapper for the xml - * @param array (key=>value) $replacements => an array with variable and object name replacements - * @param boolean $add_header => whether to add header to the xml string - * @param array (key=>value) $header_params => array with additional xml tag params - * @param string $node_name => tag name in case of numeric array key - */ - public static function generateValidXmlFromMixiedObj($obj, $wrapper = null, $replacements=array(), $add_header = true, $header_params=array(), $node_name = 'node') - { - $xml = ''; - if($add_header) - $xml .= self::generateHeader($header_params); - if($wrapper!=null) $xml .= '<' . $wrapper . '>'; - if(is_object($obj)) - { - $node_block = strtolower(get_class($obj)); - if(isset($replacements[$node_block])) $node_block = $replacements[$node_block]; - $xml .= '<' . $node_block . '>'; - $vars = get_object_vars($obj); - if(!empty($vars)) - { - foreach($vars as $var_id => $var) - { - if(isset($replacements[$var_id])) $var_id = $replacements[$var_id]; - $xml .= '<' . $var_id . '>'; - $xml .= self::generateValidXmlFromMixiedObj($var, null, $replacements, false, null, $node_name); - $xml .= ''; - } - } - $xml .= ''; - } - else if(is_array($obj)) - { - foreach($obj as $var_id => $var) - { - if(!is_object($var)) - { - if (is_numeric($var_id)) - $var_id = $node_name; - if(isset($replacements[$var_id])) $var_id = $replacements[$var_id]; - $xml .= '<' . $var_id . '>'; - } - $xml .= self::generateValidXmlFromMixiedObj($var, null, $replacements, false, null, $node_name); - if(!is_object($var)) - $xml .= ''; - } - } - else - { - $xml .= htmlspecialchars($obj, ENT_QUOTES); - } - - if($wrapper!=null) $xml .= ''; - - return $xml; - } - - /** - * - * xml header generator - * @param array $params - */ - public static function generateHeader($params = array()) - { - $basic_params = array('version' => '1.0', 'encoding' => 'UTF-8'); - if(!empty($params)) - $basic_params = array_merge($basic_params,$params); - - $header = '$v) - { - $header .= ' '.$k.'='.$v; - } - $header .= ' ?>'; - return $header; - } - - public static function arrayToXml($array, &$xml){ - foreach ($array as $key => $value) { - if(is_array($value)){ - if(is_int($key)){ - $key = "node"; - } - $label = $xml->addChild($key); - self::arrayToXml($value, $label); - } - else { - $xml->addChild($key, $value); - } - } - } - - public static function to_xml(\SimpleXMLElement $object, array $data) - { - foreach ($data as $key => $value) { - if (is_array($value)) { - $new_object = $object->addChild($key); - self::to_xml($new_object, $value); - } else { - // if the key is an integer, it needs text with it to actually work. - if ($key == (int) $key) { - $key = "key_$key"; - } - - $object->addChild($key, $value); - } - } - } -} \ No newline at end of file diff --git a/src/XML/SoapXml.php b/src/Xml/SoapXml.php similarity index 78% rename from src/XML/SoapXml.php rename to src/Xml/SoapXml.php index ae0da65..0e1c260 100644 --- a/src/XML/SoapXml.php +++ b/src/Xml/SoapXml.php @@ -1,10 +1,12 @@ length ? $list->item(0)->firstChild->nodeValue : 'No Fault Message found'; } -} \ No newline at end of file +} diff --git a/src/Xml/XMLSerializer.php b/src/Xml/XMLSerializer.php new file mode 100644 index 0000000..2c0d8c8 --- /dev/null +++ b/src/Xml/XMLSerializer.php @@ -0,0 +1,99 @@ +nodeType) { + case XML_CDATA_SECTION_NODE: + case XML_TEXT_NODE: + $output = trim($node->textContent); + break; + case XML_ELEMENT_NODE: + for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) { + $child = $node->childNodes->item($i); + $v = self::domNodeToArray($child); + if (isset($child->tagName)) { + $t = Str::after($child->tagName, ':'); + if (!isset($output[$t])) { + $output[$t] = []; + } + $output[$t][] = $v; + } elseif ($v || $v === '0') { + $output = (string) $v; + } + } + if ($node->attributes->length && !is_array($output)) { // Has attributes but isn't an array + $output = ['@content' => $output]; // Change output into an array. + } + if (is_array($output)) { + if ($node->attributes->length) { + $a = []; + foreach ($node->attributes as $attrName => $attrNode) { + $a[$attrName] = (string) $attrNode->value; + } + $output['@attributes'] = $a; + } + foreach ($output as $t => $v) { + if (is_array($v) && count($v) == 1 && $t != '@attributes') { + $output[$t] = $v[0]; + } + } + } + break; + } + return $output; + } + + /** + * Return a valid SOAP Xml + * + * @param array $array + * @return mixed + */ + public static function arrayToSoapXml(array $array) { + $array = [ + 'SOAP-ENV:Body' => $array + ]; + $xml = new SimpleXMLElement(''); + self::addArrayToXml($array, $xml); + + return $xml->asXML(); + } + + /** + * @param $array + * @param $xml + */ + public static function addArrayToXml($array, &$xml){ + foreach ($array as $key => $value) { + if(is_array($value)){ + if(is_int($key)){ + $key = "node"; + } + $label = $xml->addChild($key); + self::addArrayToXml($value, $label); + } + else { + $xml->addChild($key, $value); + } + } + } +} diff --git a/tests/Unit/SoapClientTest.php b/tests/Unit/SoapClientTest.php index 58c211d..92cef8b 100644 --- a/tests/Unit/SoapClientTest.php +++ b/tests/Unit/SoapClientTest.php @@ -1,103 +1,123 @@ -call('Get_Users'); - self::assertTrue($response->ok()); - Soap::assertSent(function (Request $request) { - return $request->action() === 'Get_Users'; - }); - Soap::assertNotSent(function (Request $request) { - return $request->action() === 'Get_User'; - }); - Soap::assertActionCalled('Get_Users'); - } - - public function testMagicCallByConfig() - { - Soap::fake(); - $response = Soap::buildClient('laravel_soap')->Get_User(); - self::assertTrue($response->ok()); - } - - public function testArrayAccessResponse() - { - Soap::fakeSequence()->push('test'); - $response = Soap::buildClient('laravel_soap')->Get_User()['response']; - self::assertEquals('test', $response); - } - - public function testSequenceFake() - { - $responseFake = ['user' => 'test']; - $responseFake2 = ['user' => 'test2']; - Soap::fakeSequence() - ->push($responseFake) - ->whenEmpty(Soap::response($responseFake2)); - $client = Soap::buildClient('laravel_soap'); - $response = $client->Get_User(); - $response2 = $client->Get_User(); - $response3 = $client->Get_User(); - self::assertTrue($response->ok()); - self::assertEquals($responseFake, $response->json()); - self::assertEquals($responseFake2, $response2->json()); - self::assertEquals($responseFake2, $response3->json()); - } - - /** - * @dataProvider soapActionProvider - * @param $action - * @param $fake - * @param $exspected - */ - public function testSoapFake($action, $fake, $exspected) - { - $fake = collect($fake)->map(function ($item) { - return Soap::response($item); - })->all(); - Soap::fake($fake); - $response = Soap::baseWsdl('https://laravel-soap.wsdl') - ->call($action); - self::assertEquals($exspected, $response->json()); - } - - public function soapActionProvider() - { - $fakeResponse = [ - 'Get_Users' => [ - 'Response_Data' => [ - 'Users' => [ - [ - 'name' => 'test', - 'field' => 'bla' - ] - ] - ] - ], - 'Get_Post' => 'Test' - ]; - return [ - 'without_fake_array' => ['Get_User', null, null], - 'with_fake_array_wrong_method' => ['Get_User', $fakeResponse, null], - 'with_fake_array' => ['Get_Users', $fakeResponse, $fakeResponse['Get_Users']], - 'with_fake_string' => ['Get_Post', $fakeResponse, ['response' => 'Test']], - ]; - } -} \ No newline at end of file +call('Get_Users'); + self::assertTrue($response->ok()); + Soap::assertSent(function (Request $request) { + return $request->action() === 'Get_Users'; + }); + Soap::assertNotSent(function (Request $request) { + return $request->action() === 'Get_User'; + }); + Soap::assertActionCalled('Get_Users'); + } + + public function testMagicCallByConfig() + { + Soap::fake(); + $response = Soap::buildClient('laravel_soap')->Get_User(); + self::assertTrue($response->ok()); + } + + public function testArrayAccessResponse() + { + Soap::fakeSequence()->push('test'); + $response = Soap::buildClient('laravel_soap')->Get_User()['response']; + self::assertEquals('test', $response); + } + + public function testRequestWithArguments() + { + Soap::fake(); + + $arguments = [ + 'prename' => 'Corona', + 'lastname' => 'Pandemic', + ]; + + /** @var Response $response */ + $response = Soap::buildClient('laravel_soap')->Submit_User($arguments); + + self::assertTrue($response->ok()); + Soap::assertSent(function (Request $request) use ($arguments) { + return $request->arguments() === $arguments && + $request->action() === 'Submit_User'; + }); + } + + public function testSequenceFake() + { + $responseFake = ['user' => 'test']; + $responseFake2 = ['user' => 'test2']; + Soap::fakeSequence() + ->push($responseFake) + ->whenEmpty(Soap::response($responseFake2)); + $client = Soap::buildClient('laravel_soap'); + $response = $client->Get_User(); + $response2 = $client->Get_User(); + $response3 = $client->Get_User(); + self::assertTrue($response->ok()); + self::assertEquals($responseFake, $response->json()); + self::assertEquals($responseFake2, $response2->json()); + self::assertEquals($responseFake2, $response3->json()); + } + + /** + * @dataProvider soapActionProvider + * @param $action + * @param $fake + * @param $exspected + */ + public function testSoapFake($action, $fake, $exspected) + { + $fake = collect($fake)->map(function ($item) { + return Soap::response($item); + })->all(); + Soap::fake($fake); + $response = Soap::baseWsdl('https://laravel-soap.wsdl') + ->call($action); + self::assertEquals($exspected, $response->json()); + } + + public function soapActionProvider() + { + $fakeResponse = [ + 'Get_Users' => [ + 'Response_Data' => [ + 'Users' => [ + [ + 'name' => 'test', + 'field' => 'bla' + ] + ] + ] + ], + 'Get_Post' => 'Test' + ]; + return [ + 'without_fake_array' => ['Get_User', null, null], + 'with_fake_array_wrong_method' => ['Get_User', $fakeResponse, null], + 'with_fake_array' => ['Get_Users', $fakeResponse, $fakeResponse['Get_Users']], + 'with_fake_string' => ['Get_Post', $fakeResponse, ['response' => 'Test']], + ]; + } +} diff --git a/tests/Unit/XML/SoapXmlTest.php b/tests/Unit/Xml/SoapXmlTest.php similarity index 95% rename from tests/Unit/XML/SoapXmlTest.php rename to tests/Unit/Xml/SoapXmlTest.php index 92dc246..202eda2 100644 --- a/tests/Unit/XML/SoapXmlTest.php +++ b/tests/Unit/Xml/SoapXmlTest.php @@ -1,6 +1,6 @@ + + + Code + dredd + + +XML; + + protected $array = [ + 'prename' => 'Code', + 'lastname' => 'dredd' + ]; + + public function testArrayToSoapXml() + { + $soapXml = XMLSerializer::arrayToSoapXml($this->array); + + self::assertXmlStringEqualsXmlString($this->xml, $soapXml); + } + + public function testDomNodeToArray() + { + $xmlDocument = SoapXml::fromString($this->xml); + $xmlBodyAsArray = XMLSerializer::domNodeToArray($xmlDocument->getBody()); + + self::assertEquals($this->array, $xmlBodyAsArray); + } +}