Skip to content

Commit 7fdafb1

Browse files
davertdavert
authored andcommitted
SOAP module renamed
1 parent 5f2405a commit 7fdafb1

File tree

1 file changed

+373
-0
lines changed

1 file changed

+373
-0
lines changed

src/Codeception/Module/SOAP.php

Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
<?php
2+
/**
3+
* Module for testing SOAP WSDL web services.
4+
* Send requests and check if response matches the pattern.
5+
*
6+
* This module can be used either with frameworks or PHPBrowser.
7+
* It tries to guess the framework is is attached to.
8+
* If a endpoint is a full url then it uses PHPBrowser.
9+
*
10+
* ### Using Inside Framework
11+
* Please note, that PHP SoapServer::handle method sends additional headers.
12+
* This may trigger warning: "Cannot modify header information"
13+
* If you use PHP SoapServer with framework, try to block call to this method in testing environment.
14+
*
15+
* ## Configuration
16+
*
17+
* * endpoint *required* - soap wsdl endpoint
18+
*
19+
* ## Public Properties
20+
*
21+
* * request - last soap request (DOMDocument)
22+
* * response - last soap response (DOMDocument)
23+
*
24+
*/
25+
26+
namespace Codeception\Module;
27+
28+
use Codeception\Util\Soap as SoapUtils;
29+
30+
class SOAP extends \Codeception\Module
31+
{
32+
33+
protected $config = array('schema' => "", 'schema_url' => 'http://schemas.xmlsoap.org/soap/envelope/');
34+
protected $requiredFields = array('endpoint');
35+
/**
36+
* @var \Symfony\Component\BrowserKit\Client
37+
*/
38+
public $client = null;
39+
public $is_functional = false;
40+
41+
/**
42+
* @var \DOMDocument
43+
*/
44+
public $xmlRequest = null;
45+
/**
46+
* @var \DOMDocument
47+
*/
48+
public $xmlResponse = null;
49+
50+
public function _before(\Codeception\TestCase $test)
51+
{
52+
if (!$this->client) {
53+
if (!strpos($this->config['endpoint'], '://')) {
54+
// not valid url
55+
foreach ($this->getModules() as $module) {
56+
if ($module instanceof \Codeception\Util\Framework) {
57+
$this->client = $module->client;
58+
$this->is_functional = true;
59+
break;
60+
}
61+
}
62+
} else {
63+
if (!$this->hasModule('PhpBrowser'))
64+
throw new \Codeception\Exception\ModuleConfig(__CLASS__, "For Soap testing via HTTP please enable PhpBrowser module");
65+
$this->client = $this->getModule('PhpBrowser')->session->getDriver()->getClient();
66+
}
67+
if (!$this->client) throw new \Codeception\Exception\ModuleConfig(__CLASS__, "Client for SOAP requests not initialized.\nProvide either PhpBrowser module or Framework module which shares FrameworkInterface");
68+
}
69+
70+
$this->buildRequest();
71+
$this->xmlResponse = null;
72+
}
73+
74+
/**
75+
* Prepare SOAP header.
76+
* Receives header name and parameters as array.
77+
*
78+
* Example:
79+
*
80+
* ``` php
81+
* <?php
82+
* $I->haveSoapHeader('AuthHeader', array('username' => 'davert', 'password' => '123345'));
83+
* ```
84+
*
85+
* Will produce header:
86+
*
87+
* ```
88+
* <soapenv:Header>
89+
* <SessionHeader>
90+
* <AuthHeader>
91+
* <username>davert</username>
92+
* <password>12345</password>
93+
* </AuthHeader>
94+
* </soapenv:Header>
95+
* ```
96+
*
97+
* @param $header
98+
* @param array $params
99+
*/
100+
public function haveSoapHeader($header, $params = array())
101+
{
102+
$soap_schema_url = $this->config['schema_url'];
103+
$xml = $this->xmlRequest;
104+
$xmlHeader = $xml->documentElement->getElementsByTagNameNS($soap_schema_url, 'Header')->item(0);
105+
$headerEl = $xml->createElement($header);
106+
SoapUtils::arrayToXml($xml, $headerEl, $params);
107+
$xmlHeader->appendChild($headerEl);
108+
}
109+
110+
/**
111+
* Submits request to endpoint.
112+
*
113+
* Requires of api function name and parameters.
114+
* Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes).
115+
*
116+
* You are allowed to execute as much requests as you need inside test.
117+
*
118+
* Example:
119+
*
120+
* ``` php
121+
* $I->sendRequest('UpdateUser', '<user><id>1</id><name>notdavert</name></user>');
122+
* $I->sendRequest('UpdateUser', \Codeception\Utils\Soap::request()->user
123+
* ->id->val(1)->parent()
124+
* ->name->val('notdavert');
125+
* ```
126+
*
127+
* @param $request
128+
* @param $body
129+
*/
130+
public function sendSoapRequest($action, $body = "")
131+
{
132+
$soap_schema_url = $this->config['schema_url'];
133+
$xml = $this->xmlRequest;
134+
$call = $xml->createElement('ns:' . $action);
135+
if ($body) {
136+
$bodyXml = SoapUtils::toXml($body);
137+
$bodyNode = $xml->importNode($bodyXml->documentElement, true);
138+
$call->appendChild($bodyNode);
139+
}
140+
141+
$xmlBody = $xml->getElementsByTagNameNS($soap_schema_url, 'Body')->item(0);
142+
143+
// cleanup if body already set
144+
foreach ($xmlBody->childNodes as $node) {
145+
$xmlBody->removeChild($node);
146+
}
147+
148+
$xmlBody->appendChild($call);
149+
$this->debugSection("Request", $req = $xml->C14N());
150+
151+
if ($this->is_functional) {
152+
$response = $this->processInternalRequest($action, $req);
153+
} else {
154+
$response = $this->processExternalRequest($action, $req);
155+
}
156+
157+
$this->debugSection("Response", $response);
158+
$this->xmlResponse = SoapUtils::toXml($response);
159+
}
160+
161+
/**
162+
* Checks XML response equals provided XML.
163+
* Comparison is done by canonicalizing both xml`s.
164+
*
165+
* Parameters can be passed either as DOMDocument, DOMNode, XML string, or array (if no attributes).
166+
*
167+
* Example:
168+
*
169+
* ``` php
170+
* <?php
171+
* $I->seeSoapResponseEquals("<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope><SOAP-ENV:Body><result>1</result></SOAP-ENV:Envelope>");
172+
*
173+
* $dom = new \DOMDocument();
174+
* $dom->load($file);
175+
* $I->seeSoapRequestIncludes($dom);
176+
*
177+
* ```
178+
*
179+
* @param $xml
180+
*/
181+
public function seeSoapResponseEquals($xml)
182+
{
183+
$xml = SoapUtils::toXml($xml);
184+
\PHPUnit_Framework_Assert::assertEquals($this->xmlResponse->C14N(), $xml->C14N());
185+
}
186+
187+
/**
188+
* Checks XML response includes provided XML.
189+
* Comparison is done by canonicalizing both xml`s.
190+
* Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
191+
*
192+
* Example:
193+
*
194+
* ``` php
195+
* <?php
196+
* $I->seeSoapResponseIncludes("<result>1</result>");
197+
* $I->seeSoapRequestIncludes(\Codeception\Utils\Soap::response()->result->val(1));
198+
*
199+
* $dom = new \DDOMDocument();
200+
* $dom->load('template.xml');
201+
* $I->seeSoapRequestIncludes($dom);
202+
* ?>
203+
* ```
204+
*
205+
* @param $xml
206+
*/
207+
public function seeSoapResponseIncludes($xml)
208+
{
209+
$xml = $this->canonicalize($xml);
210+
\PHPUnit_Framework_Assert::assertContains($xml, $this->xmlResponse->C14N(), "found in XML Response");
211+
}
212+
213+
214+
/**
215+
* Checks XML response equals provided XML.
216+
* Comparison is done by canonicalizing both xml`s.
217+
*
218+
* Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
219+
*
220+
* @param $xml
221+
*/
222+
public function dontSeeSoapResponseEquals($xml)
223+
{
224+
$xml = SoapUtils::toXml($xml);
225+
\PHPUnit_Framework_Assert::assertXmlStringNotEqualsXmlString($this->xmlResponse->C14N(), $xml->C14N());
226+
}
227+
228+
229+
/**
230+
* Checks XML response does not include provided XML.
231+
* Comparison is done by canonicalizing both xml`s.
232+
* Parameter can be passed either as XmlBuilder, DOMDocument, DOMNode, XML string, or array (if no attributes).
233+
*
234+
* @param $xml
235+
*/
236+
public function dontSeeSoapResponseIncludes($xml)
237+
{
238+
$xml = $this->canonicalize($xml);
239+
\PHPUnit_Framework_Assert::assertNotContains($xml, $this->xmlResponse->C14N(), "found in XML Response");
240+
}
241+
242+
/**
243+
* Checks XML response contains provided structure.
244+
* Response elements will be compared with XML provided.
245+
* Only nodeNames are checked to see elements match.
246+
*
247+
* Example:
248+
*
249+
* ``` php
250+
* <?php
251+
*
252+
* $I->seeResponseContains("<user><query>CreateUser<name>Davert</davert></user>");
253+
* $I->seeSoapResponseContainsStructure("<query><name></name></query>");
254+
* ?>
255+
* ```
256+
*
257+
* Use this method to check XML of valid structure is returned.
258+
* This method doesn't use schema for validation.
259+
* This method dosn't require whole response XML to match the structure.
260+
*
261+
* @param $xml
262+
*/
263+
public function seeSoapResponseContainsStructure($xml) {
264+
$xml = SoapUtils::toXml($xml);
265+
$this->debugSection("Structure", $xml->saveXML());
266+
$root = $xml->firstChild;
267+
268+
$this->debugSection("Structure Root", $root->nodeName);
269+
270+
$els = $this->xmlResponse->getElementsByTagName($root->nodeName);
271+
272+
if (empty($els)) return \PHPUnit_Framework_Assert::fail("Element {$root->nodeName} not found in response");
273+
274+
$matches = false;
275+
foreach ($els as $node) {
276+
$matches |= $this->structureMatches($root, $node);
277+
}
278+
\PHPUnit_Framework_Assert::assertTrue((bool)$matches, "this structure is in response");
279+
280+
}
281+
282+
/**
283+
* Checks response code from server.
284+
*
285+
* @param $code
286+
*/
287+
public function seeResponseCodeIs($code) {
288+
\PHPUnit_Framework_Assert::assertEquals($code, $this->client->getResponse()->getStatusCode(), "soap response code matches expected");
289+
}
290+
291+
protected function structureMatches($schema, $xml)
292+
{
293+
foreach ($schema->childNodes as $node1) {
294+
$matched = false;
295+
foreach ($xml->childNodes as $node2) {
296+
if ($node1->nodeName == $node2->nodeName) {
297+
$matched = $this->structureMatches($node1, $node2);
298+
if ($matched) break;
299+
}
300+
}
301+
if (!$matched) return false;
302+
}
303+
return true;
304+
}
305+
306+
protected function getSchema()
307+
{
308+
return $this->config['schema'];
309+
}
310+
311+
protected function canonicalize($xml)
312+
{
313+
$xml = SoapUtils::toXml($xml)->C14N();
314+
return $xml;
315+
}
316+
317+
/**
318+
* @return \DOMDocument
319+
*/
320+
protected function buildRequest()
321+
{
322+
$soap_schema_url = $this->config['schema_url'];
323+
$xml = new \DOMDocument();
324+
$root = $xml->createElement('soapenv:Envelope');
325+
$xml->appendChild($root);
326+
$root->setAttribute('xmlns:ns', $this->getSchema());
327+
$root->setAttribute('xmlns:soapenv', $soap_schema_url);
328+
$body = $xml->createElementNS($soap_schema_url, 'soapenv:Body');
329+
$header = $xml->createElementNS($soap_schema_url, 'soapenv:Header');
330+
$root->appendChild($header);
331+
$root->appendChild($body);
332+
$this->xmlRequest = $xml;
333+
return $xml;
334+
}
335+
336+
protected function processRequest($action, $body)
337+
{
338+
$this->client->request('POST',
339+
$this->config['endpoint'],
340+
array(), array(),
341+
array(
342+
"HTTP_Content-Type" => "text/xml; charset=UTF-8",
343+
'HTTP_Content-Length' => strlen($body),
344+
'HTTP_SOAPAction' => $action),
345+
$body
346+
);
347+
}
348+
349+
protected function processInternalRequest($action, $body)
350+
{
351+
ob_start();
352+
try {
353+
$this->client->setServerParameter('HTTP_HOST', 'localhost');
354+
$this->processRequest($action, $body);
355+
} catch (\ErrorException $e) {
356+
// Zend_Soap outputs warning as an exception
357+
if (strpos($e->getMessage(),'Warning: Cannot modify header information')===false) {
358+
ob_end_clean();
359+
throw $e;
360+
}
361+
}
362+
$response = ob_get_contents();
363+
ob_end_clean();
364+
return $response;
365+
}
366+
367+
protected function processExternalRequest($action, $body)
368+
{
369+
$this->processRequest($action, $body);
370+
return $this->client->getResponse()->getContent();
371+
}
372+
373+
}

0 commit comments

Comments
 (0)