PageRenderTime 27ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/daisymtw/vendor/codeception/base/src/Codeception/Module/SOAP.php

https://gitlab.com/panace/public
PHP | 512 lines | 246 code | 43 blank | 223 comment | 13 complexity | 1bcf0be63e0b8687d13c9d6b83c25056 MD5 | raw file
  1. <?php
  2. namespace Codeception\Module;
  3. use Codeception\Lib\Interfaces\API;
  4. use Codeception\Lib\Interfaces\DependsOnModule;
  5. use Codeception\Lib\Notification;
  6. use Codeception\Module as CodeceptionModule;
  7. use Codeception\TestInterface;
  8. use Codeception\Exception\ModuleException;
  9. use Codeception\Exception\ModuleRequireException;
  10. use Codeception\Lib\Framework;
  11. use Codeception\Lib\InnerBrowser;
  12. use Codeception\Util\Soap as SoapUtils;
  13. use Codeception\Util\XmlStructure;
  14. /**
  15. * Module for testing SOAP WSDL web services.
  16. * Send requests and check if response matches the pattern.
  17. *
  18. * This module can be used either with frameworks or PHPBrowser.
  19. * It tries to guess the framework is is attached to.
  20. * If a endpoint is a full url then it uses PHPBrowser.
  21. *
  22. * ### Using Inside Framework
  23. *
  24. * Please note, that PHP SoapServer::handle method sends additional headers.
  25. * This may trigger warning: "Cannot modify header information"
  26. * If you use PHP SoapServer with framework, try to block call to this method in testing environment.
  27. *
  28. * ## Status
  29. *
  30. * * Maintainer: **davert**
  31. * * Stability: **stable**
  32. * * Contact: codecept@davert.mail.ua
  33. *
  34. * ## Configuration
  35. *
  36. * * endpoint *required* - soap wsdl endpoint
  37. * * SOAPAction - replace SOAPAction HTTP header (Set to '' to SOAP 1.2)
  38. *
  39. * ## Public Properties
  40. *
  41. * * xmlRequest - last SOAP request (DOMDocument)
  42. * * xmlResponse - last SOAP response (DOMDocument)
  43. *
  44. */
  45. class SOAP extends CodeceptionModule implements DependsOnModule
  46. {
  47. protected $config = [
  48. 'schema' => "",
  49. 'schema_url' => 'http://schemas.xmlsoap.org/soap/envelope/',
  50. 'framework_collect_buffer' => true
  51. ];
  52. protected $requiredFields = ['endpoint'];
  53. protected $dependencyMessage = <<<EOF
  54. Example using PhpBrowser as backend for SOAP module.
  55. --
  56. modules:
  57. enabled:
  58. - SOAP:
  59. depends: PhpBrowser
  60. --
  61. Framework modules can be used as well for functional testing of SOAP API.
  62. EOF;
  63. /**
  64. * @var \Symfony\Component\BrowserKit\Client
  65. */
  66. public $client = null;
  67. public $isFunctional = false;
  68. /**
  69. * @var \DOMDocument
  70. */
  71. public $xmlRequest = null;
  72. /**
  73. * @var \DOMDocument
  74. */
  75. public $xmlResponse = null;
  76. /**
  77. * @var XmlStructure
  78. */
  79. protected $xmlStructure = null;
  80. /**
  81. * @var InnerBrowser
  82. */
  83. protected $connectionModule;
  84. public function _before(TestInterface $test)
  85. {
  86. $this->client = &$this->connectionModule->client;
  87. $this->buildRequest();
  88. $this->xmlResponse = null;
  89. $this->xmlStructure = null;
  90. }
  91. protected function onReconfigure()
  92. {
  93. $this->buildRequest();
  94. $this->xmlResponse = null;
  95. $this->xmlStructure = null;
  96. }
  97. public function _depends()
  98. {
  99. return ['Codeception\Lib\InnerBrowser' => $this->dependencyMessage];
  100. }
  101. public function _inject(InnerBrowser $connectionModule)
  102. {
  103. $this->connectionModule = $connectionModule;
  104. if ($connectionModule instanceof Framework) {
  105. $this->isFunctional = true;
  106. }
  107. }
  108. private function getClient()
  109. {
  110. if (!$this->client) {
  111. throw new ModuleRequireException($this, "Connection client is not available.");
  112. }
  113. return $this->client;
  114. }
  115. private function getXmlResponse()
  116. {
  117. if (!$this->xmlResponse) {
  118. throw new ModuleException($this, "No XML response, use `\$I->sendSoapRequest` to receive it");
  119. }
  120. return $this->xmlResponse;
  121. }
  122. private function getXmlStructure()
  123. {
  124. if (!$this->xmlStructure) {
  125. $this->xmlStructure = new XmlStructure($this->getXmlResponse());
  126. }
  127. return $this->xmlStructure;
  128. }
  129. /**
  130. * Prepare SOAP header.
  131. * Receives header name and parameters as array.
  132. *
  133. * Example:
  134. *
  135. * ``` php
  136. * <?php
  137. * $I->haveSoapHeader('AuthHeader', array('username' => 'davert', 'password' => '123345'));
  138. * ```
  139. *
  140. * Will produce header:
  141. *
  142. * ```
  143. * <soapenv:Header>
  144. * <SessionHeader>
  145. * <AuthHeader>
  146. * <username>davert</username>
  147. * <password>12345</password>
  148. * </AuthHeader>
  149. * </soapenv:Header>
  150. * ```
  151. *
  152. * @param $header
  153. * @param array $params
  154. */
  155. public function haveSoapHeader($header, $params = [])
  156. {
  157. $soap_schema_url = $this->config['schema_url'];
  158. $xml = $this->xmlRequest;
  159. $xmlHeader = $xml->documentElement->getElementsByTagNameNS($soap_schema_url, 'Header')->item(0);
  160. $headerEl = $xml->createElement($header);
  161. SoapUtils::arrayToXml($xml, $headerEl, $params);
  162. $xmlHeader->appendChild($headerEl);
  163. }
  164. /**
  165. * Submits request to endpoint.
  166. *
  167. * Requires of api function name and parameters.
  168. * Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes).
  169. *
  170. * You are allowed to execute as much requests as you need inside test.
  171. *
  172. * Example:
  173. *
  174. * ``` php
  175. * $I->sendSoapRequest('UpdateUser', '<user><id>1</id><name>notdavert</name></user>');
  176. * $I->sendSoapRequest('UpdateUser', \Codeception\Utils\Soap::request()->user
  177. * ->id->val(1)->parent()
  178. * ->name->val('notdavert');
  179. * ```
  180. *
  181. * @param $request
  182. * @param $body
  183. */
  184. public function sendSoapRequest($action, $body = "")
  185. {
  186. $soap_schema_url = $this->config['schema_url'];
  187. $xml = $this->xmlRequest;
  188. $call = $xml->createElement('ns:' . $action);
  189. if ($body) {
  190. $bodyXml = SoapUtils::toXml($body);
  191. if ($bodyXml->hasChildNodes()) {
  192. foreach ($bodyXml->childNodes as $bodyChildNode) {
  193. $bodyNode = $xml->importNode($bodyChildNode, true);
  194. $call->appendChild($bodyNode);
  195. }
  196. }
  197. }
  198. $xmlBody = $xml->getElementsByTagNameNS($soap_schema_url, 'Body')->item(0);
  199. // cleanup if body already set
  200. foreach ($xmlBody->childNodes as $node) {
  201. $xmlBody->removeChild($node);
  202. }
  203. $xmlBody->appendChild($call);
  204. $this->debugSection("Request", $req = $xml->C14N());
  205. if ($this->isFunctional && $this->config['framework_collect_buffer']) {
  206. $response = $this->processInternalRequest($action, $req);
  207. } else {
  208. $response = $this->processExternalRequest($action, $req);
  209. }
  210. $this->debugSection("Response", $response);
  211. $this->xmlResponse = SoapUtils::toXml($response);
  212. $this->xmlStructure = null;
  213. }
  214. /**
  215. * Checks XML response equals provided XML.
  216. * Comparison is done by canonicalizing both xml`s.
  217. *
  218. * Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes).
  219. *
  220. * Example:
  221. *
  222. * ``` php
  223. * <?php
  224. * $I->seeSoapResponseEquals("<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope><SOAP-ENV:Body><result>1</result></SOAP-ENV:Envelope>");
  225. *
  226. * $dom = new \DOMDocument();
  227. * $dom->load($file);
  228. * $I->seeSoapRequestIncludes($dom);
  229. *
  230. * ```
  231. *
  232. * @param $xml
  233. */
  234. public function seeSoapResponseEquals($xml)
  235. {
  236. $xml = SoapUtils::toXml($xml);
  237. $this->assertEquals($xml->C14N(), $this->getXmlResponse()->C14N());
  238. }
  239. /**
  240. * Checks XML response includes provided XML.
  241. * Comparison is done by canonicalizing both xml`s.
  242. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
  243. *
  244. * Example:
  245. *
  246. * ``` php
  247. * <?php
  248. * $I->seeSoapResponseIncludes("<result>1</result>");
  249. * $I->seeSoapRequestIncludes(\Codeception\Utils\Soap::response()->result->val(1));
  250. *
  251. * $dom = new \DDOMDocument();
  252. * $dom->load('template.xml');
  253. * $I->seeSoapRequestIncludes($dom);
  254. * ?>
  255. * ```
  256. *
  257. * @param $xml
  258. */
  259. public function seeSoapResponseIncludes($xml)
  260. {
  261. $xml = $this->canonicalize($xml);
  262. $this->assertContains($xml, $this->getXmlResponse()->C14N(), "found in XML Response");
  263. }
  264. /**
  265. * Checks XML response equals provided XML.
  266. * Comparison is done by canonicalizing both xml`s.
  267. *
  268. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
  269. *
  270. * @param $xml
  271. */
  272. public function dontSeeSoapResponseEquals($xml)
  273. {
  274. $xml = SoapUtils::toXml($xml);
  275. \PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlString($xml->C14N(), $this->getXmlResponse()->C14N());
  276. }
  277. /**
  278. * Checks XML response does not include provided XML.
  279. * Comparison is done by canonicalizing both xml`s.
  280. * Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
  281. *
  282. * @param $xml
  283. */
  284. public function dontSeeSoapResponseIncludes($xml)
  285. {
  286. $xml = $this->canonicalize($xml);
  287. $this->assertNotContains($xml, $this->getXmlResponse()->C14N(), "found in XML Response");
  288. }
  289. /**
  290. * Checks XML response contains provided structure.
  291. * Response elements will be compared with XML provided.
  292. * Only nodeNames are checked to see elements match.
  293. *
  294. * Example:
  295. *
  296. * ``` php
  297. * <?php
  298. *
  299. * $I->seeSoapResponseContainsStructure("<query><name></name></query>");
  300. * ?>
  301. * ```
  302. *
  303. * Use this method to check XML of valid structure is returned.
  304. * This method does not use schema for validation.
  305. * This method does not require path from root to match the structure.
  306. *
  307. * @param $xml
  308. */
  309. public function seeSoapResponseContainsStructure($xml)
  310. {
  311. $xml = SoapUtils::toXml($xml);
  312. $this->debugSection("Structure", $xml->saveXML());
  313. $this->assertTrue((bool)$this->getXmlStructure()->matchXmlStructure($xml), "this structure is in response");
  314. }
  315. /**
  316. * Opposite to `seeSoapResponseContainsStructure`
  317. * @param $xml
  318. */
  319. public function dontSeeSoapResponseContainsStructure($xml)
  320. {
  321. $xml = SoapUtils::toXml($xml);
  322. $this->debugSection("Structure", $xml->saveXML());
  323. $this->assertFalse((bool)$this->getXmlStructure()->matchXmlStructure($xml), "this structure is in response");
  324. }
  325. /**
  326. * Checks XML response with XPath locator
  327. *
  328. * ``` php
  329. * <?php
  330. * $I->seeSoapResponseContainsXPath('//root/user[@id=1]');
  331. * ?>
  332. * ```
  333. *
  334. * @param $xpath
  335. */
  336. public function seeSoapResponseContainsXPath($xpath)
  337. {
  338. $this->assertTrue($this->getXmlStructure()->matchesXpath($xpath));
  339. }
  340. /**
  341. * Checks XML response doesn't contain XPath locator
  342. *
  343. * ``` php
  344. * <?php
  345. * $I->dontSeeSoapResponseContainsXPath('//root/user[@id=1]');
  346. * ?>
  347. * ```
  348. *
  349. * @param $xpath
  350. */
  351. public function dontSeeSoapResponseContainsXPath($xpath)
  352. {
  353. $this->assertFalse($this->getXmlStructure()->matchesXpath($xpath));
  354. }
  355. /**
  356. * Checks response code from server.
  357. *
  358. * @param $code
  359. */
  360. public function seeSoapResponseCodeIs($code)
  361. {
  362. $this->assertEquals(
  363. $code,
  364. $this->client->getInternalResponse()->getStatus(),
  365. "soap response code matches expected"
  366. );
  367. }
  368. /**
  369. * @deprecated use seeSoapResponseCodeIs instead
  370. */
  371. public function seeResponseCodeIs($code)
  372. {
  373. Notification::deprecate('SOAP::seeResponseCodeIs deprecated in favor of seeSoapResponseCodeIs', 'SOAP Module');
  374. $this->seeSoapResponseCodeIs($code);
  375. }
  376. /**
  377. * Finds and returns text contents of element.
  378. * Element is matched by either CSS or XPath
  379. *
  380. * @version 1.1
  381. * @param $cssOrXPath
  382. * @return string
  383. */
  384. public function grabTextContentFrom($cssOrXPath)
  385. {
  386. $el = $this->getXmlStructure()->matchElement($cssOrXPath);
  387. return $el->textContent;
  388. }
  389. /**
  390. * Finds and returns attribute of element.
  391. * Element is matched by either CSS or XPath
  392. *
  393. * @version 1.1
  394. * @param $cssOrXPath
  395. * @param $attribute
  396. * @return string
  397. */
  398. public function grabAttributeFrom($cssOrXPath, $attribute)
  399. {
  400. $el = $this->getXmlStructure()->matchElement($cssOrXPath);
  401. if (!$el->hasAttribute($attribute)) {
  402. $this->fail("Attribute not found in element matched by '$cssOrXPath'");
  403. }
  404. return $el->getAttribute($attribute);
  405. }
  406. protected function getSchema()
  407. {
  408. return $this->config['schema'];
  409. }
  410. protected function canonicalize($xml)
  411. {
  412. return SoapUtils::toXml($xml)->C14N();
  413. }
  414. /**
  415. * @return \DOMDocument
  416. */
  417. protected function buildRequest()
  418. {
  419. $soap_schema_url = $this->config['schema_url'];
  420. $xml = new \DOMDocument();
  421. $root = $xml->createElement('soapenv:Envelope');
  422. $xml->appendChild($root);
  423. $root->setAttribute('xmlns:ns', $this->getSchema());
  424. $root->setAttribute('xmlns:soapenv', $soap_schema_url);
  425. $body = $xml->createElementNS($soap_schema_url, 'soapenv:Body');
  426. $header = $xml->createElementNS($soap_schema_url, 'soapenv:Header');
  427. $root->appendChild($header);
  428. $root->appendChild($body);
  429. $this->xmlRequest = $xml;
  430. return $xml;
  431. }
  432. protected function processRequest($action, $body)
  433. {
  434. $this->getClient()->request(
  435. 'POST',
  436. $this->config['endpoint'],
  437. [],
  438. [],
  439. [
  440. 'HTTP_Content-Type' => 'text/xml; charset=UTF-8',
  441. 'HTTP_Content-Length' => strlen($body),
  442. 'HTTP_SOAPAction' => isset($this->config['SOAPAction']) ? $this->config['SOAPAction'] : $action
  443. ],
  444. $body
  445. );
  446. }
  447. protected function processInternalRequest($action, $body)
  448. {
  449. ob_start();
  450. try {
  451. $this->getClient()->setServerParameter('HTTP_HOST', 'localhost');
  452. $this->processRequest($action, $body);
  453. } catch (\ErrorException $e) {
  454. // Zend_Soap outputs warning as an exception
  455. if (strpos($e->getMessage(), 'Warning: Cannot modify header information') === false) {
  456. ob_end_clean();
  457. throw $e;
  458. }
  459. }
  460. $response = ob_get_contents();
  461. ob_end_clean();
  462. return $response;
  463. }
  464. protected function processExternalRequest($action, $body)
  465. {
  466. $this->processRequest($action, $body);
  467. return $this->client->getInternalResponse()->getContent();
  468. }
  469. }