Completed
Push — master ( 8532b6...233c5a )
by Jan
02:42
created

ApiClientTest::testRequests()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 69
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 69
rs 8.56
cc 6
eloc 47
nc 6
nop 8

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php declare(strict_types = 1);
2
3
namespace SlevomatCsobGateway\Api;
4
5
use SlevomatCsobGateway\Crypto\CryptoService;
6
use SlevomatCsobGateway\Crypto\SignatureDataFormatter;
7
8
class ApiClientTest extends \PHPUnit_Framework_TestCase
9
{
10
11
	const API_URL = 'http://foo.csob.cz';
12
13
	public function getRequests()
14
	{
15
		return [
16
			[
17
				new HttpMethod(HttpMethod::GET),
18
				'fooUrl/{dttm}/{signature}',
19
				'fooUrl/\\d{14}/signature',
20
				[],
21
				null,
22
				[
23
					'bar' => 2,
24
				],
25
				new ResponseCode(ResponseCode::S200_OK),
26
				[
27
					'header' => 'value',
28
				],
29
			],
30
			[
31
				new HttpMethod(HttpMethod::GET),
32
				'fooUrl/{fooId}/{dttm}/{signature}',
33
				'fooUrl/3/\\d{14}/signature',
34
				[
35
					'fooId' => 3,
36
				],
37
				null,
38
				[
39
					'bar' => 2,
40
				],
41
				new ResponseCode(ResponseCode::S200_OK),
42
				[
43
					'header' => 'value',
44
				],
45
			],
46
			[
47
				new HttpMethod(HttpMethod::POST),
48
				'fooUrl',
49
				'fooUrl',
50
				[
51
					'foo' => 1,
52
				],
53
				[
54
					'foo' => 1,
55
				],
56
				[
57
					'bar' => 2,
58
				],
59
				new ResponseCode(ResponseCode::S200_OK),
60
				[
61
					'header' => 'value',
62
				],
63
			],
64
			[
65
				new HttpMethod(HttpMethod::PUT),
66
				'fooUrl',
67
				'fooUrl',
68
				[
69
					'foo' => 1,
70
				],
71
				[
72
					'foo' => 1,
73
				],
74
				[
75
					'bar' => 2,
76
				],
77
				new ResponseCode(ResponseCode::S200_OK),
78
				[
79
					'header' => 'value',
80
				],
81
			],
82
			[
83
				new HttpMethod(HttpMethod::GET),
84
				'fooUrl/{dttm}/{signature}',
85
				'fooUrl/\\d{14}/signature',
86
				[],
87
				null,
88
				null,
89
				new ResponseCode(ResponseCode::S303_SEE_OTHER),
90
				[
91
					'header' => 'value',
92
				],
93
			],
94
		];
95
	}
96
97
	/**
98
	 * @param HttpMethod $httpMethod
99
	 * @param string $url
100
	 * @param string $expectedUrl
101
	 * @param mixed[] $requestData
102
	 * @param mixed[]|null $expectedRequestData
103
	 * @param mixed[]|null $responseData
104
	 * @param ResponseCode $responseCode
105
	 * @param mixed[] $responseHeaders
106
	 *
107
	 * @dataProvider getRequests
108
	 */
109
	public function testRequests(HttpMethod $httpMethod, string $url, string $expectedUrl, array $requestData, array $expectedRequestData = null, array $responseData = null, ResponseCode $responseCode, array $responseHeaders)
110
	{
111
		$cryptoService = $this->getMockBuilder(CryptoService::class)
112
			->disableOriginalConstructor()
113
			->getMock();
114
115
		$cryptoService->expects(self::any())
116
			->method('signData')
117
			->willReturn('signature');
118
119
		$cryptoService->expects(self::any())
120
			->method('verifyData')
121
			->willReturn(true);
122
123
		/** @var CryptoService $cryptoService */
124
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
125
			->getMock();
126
127
		if ($httpMethod->equalsValue(HttpMethod::GET)) {
128
			$apiClientDriver->expects(self::once())
129
				->method('request')
130
				->with($httpMethod, $this->matchesRegularExpression(sprintf('~^%s/%s$~', preg_quote(self::API_URL, '~'), $expectedUrl)), $expectedRequestData)
131
				->willReturn(new Response(
132
					$responseCode,
133
					($responseData !== null ? $responseData : []) + [
134
						'signature' => 'signature',
135
					],
136
					$responseHeaders
137
				));
138
139
		} else {
140
			$apiClientDriver->expects(self::once())
141
				->method('request')
142
				->willReturnCallback(function (HttpMethod $method, string $url, array $requestData) use ($httpMethod, $expectedUrl, $expectedRequestData, $responseCode, $responseData, $responseHeaders): Response {
143
					$this->assertEquals($httpMethod, $method);
144
					$this->assertSame(sprintf('%s/%s', self::API_URL, $expectedUrl), $url);
145
					$dttm = $requestData['dttm'];
146
					$this->assertRegExp('~^\\d{14}$~', $dttm);
147
					unset($requestData['dttm']);
148
					$this->assertEquals($expectedRequestData + ['signature' => 'signature'], $requestData);
149
150
					return new Response(
151
						$responseCode,
152
						($responseData !== null ? $responseData : []) + [
153
							'signature' => 'signature',
154
						],
155
						$responseHeaders
156
					);
157
				});
158
		}
159
160
		/** @var ApiClientDriver $apiClientDriver */
161
		$apiClient = new ApiClient($apiClientDriver, $cryptoService, self::API_URL);
162
163
		if ($httpMethod->equalsValue(HttpMethod::GET)) {
164
			$response = $apiClient->get($url, $requestData, new SignatureDataFormatter([]), new SignatureDataFormatter([]));
165
166
		} elseif ($httpMethod->equalsValue(HttpMethod::POST)) {
167
			$response = $apiClient->post($url, $requestData, new SignatureDataFormatter([]), new SignatureDataFormatter([]));
168
169
		} else {
170
			$response = $apiClient->put($url, $requestData, new SignatureDataFormatter([]), new SignatureDataFormatter([]));
171
		}
172
173
		$this->assertInstanceOf(Response::class, $response);
174
		$this->assertSame($responseCode->getValue(), $response->getResponseCode()->getValue());
175
		$this->assertEquals($responseHeaders, $response->getHeaders());
176
		$this->assertEquals($responseData, $response->getData());
177
	}
178
179
	public function getTestExceptions(): array
180
	{
181
		return [
182
			[
183
				new Response(
184
					new ResponseCode(ResponseCode::S400_BAD_REQUEST),
185
					[]
186
				),
187
				BadRequestException::class,
188
			],
189
			[
190
				new Response(
191
					new ResponseCode(ResponseCode::S403_FORBIDDEN),
192
					[]
193
				),
194
				ForbiddenException::class,
195
			],
196
			[
197
				new Response(
198
					new ResponseCode(ResponseCode::S404_NOT_FOUND),
199
					[]
200
				),
201
				NotFoundException::class,
202
			],
203
			[
204
				new Response(
205
					new ResponseCode(ResponseCode::S405_METHOD_NOT_ALLOWED),
206
					[]
207
				),
208
				MethodNotAllowedException::class,
209
			],
210
			[
211
				new Response(
212
					new ResponseCode(ResponseCode::S429_TOO_MANY_REQUESTS),
213
					[]
214
				),
215
				TooManyRequestsException::class,
216
			],
217
			[
218
				new Response(
219
					new ResponseCode(ResponseCode::S503_SERVICE_UNAVAILABLE),
220
					[]
221
				),
222
				ServiceUnavailableException::class,
223
			],
224
			[
225
				new Response(
226
					new ResponseCode(ResponseCode::S500_INTERNAL_ERROR),
227
					[]
228
				),
229
				InternalErrorException::class,
230
			],
231
		];
232
	}
233
234
	/**
235
	 * @param Response $response
236
	 * @param string $expectedExceptionClass
237
	 *
238
	 * @dataProvider getTestExceptions
239
	 */
240
	public function testExceptions(Response $response, string $expectedExceptionClass)
241
	{
242
		$cryptoService = $this->getMockBuilder(CryptoService::class)
243
			->disableOriginalConstructor()
244
			->getMock();
245
246
		$cryptoService->expects(self::once())
247
			->method('signData')
248
			->willReturn('signature');
249
250
		$cryptoService->expects(self::any())
251
			->method('verifyData')
252
			->willReturn(true);
253
254
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
255
			->getMock();
256
257
		$apiClientDriver->expects(self::once())
258
			->method('request')
259
			->willReturn($response);
260
261
		/** @var CryptoService $cryptoService */
262
		/** @var ApiClientDriver $apiClientDriver */
263
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
264
265
		try {
266
			$apiClient->get('foo/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]));
267
			$this->fail();
268
269
		} catch (RequestException $e) {
270
			$this->assertInstanceOf($expectedExceptionClass, $e);
271
			$this->assertSame($response, $e->getResponse());
272
		}
273
	}
274
275
	public function testMissingSignature()
276
	{
277
		$response = new Response(
278
			new ResponseCode(ResponseCode::S200_OK),
279
			[]
280
		);
281
282
		$cryptoService = $this->getMockBuilder(CryptoService::class)
283
			->disableOriginalConstructor()
284
			->getMock();
285
286
		$cryptoService->expects(self::once())
287
			->method('signData')
288
			->willReturn('signature');
289
290
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
291
			->getMock();
292
293
		$apiClientDriver->expects(self::once())
294
			->method('request')
295
			->willReturn($response);
296
297
		/** @var CryptoService $cryptoService */
298
		/** @var ApiClientDriver $apiClientDriver */
299
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
300
301
		try {
302
			$apiClient->get('foo/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]));
303
			$this->fail();
304
305
		} catch (InvalidSignatureException $e) {
306
			$this->assertSame($response->getData(), $e->getResponseData());
307
		}
308
	}
309
310
	public function testInvalidSignature()
311
	{
312
		$response = new Response(
313
			new ResponseCode(ResponseCode::S200_OK),
314
			[
315
				'signature' => 'invalidSignature',
316
			]
317
		);
318
319
		$cryptoService = $this->getMockBuilder(CryptoService::class)
320
			->disableOriginalConstructor()
321
			->getMock();
322
323
		$cryptoService->expects(self::once())
324
			->method('signData')
325
			->willReturn('signature');
326
327
		$cryptoService->expects(self::any())
328
			->method('verifyData')
329
			->willReturn(false);
330
331
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
332
			->getMock();
333
334
		$apiClientDriver->expects(self::once())
335
			->method('request')
336
			->willReturn($response);
337
338
		/** @var CryptoService $cryptoService */
339
		/** @var ApiClientDriver $apiClientDriver */
340
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
341
342
		try {
343
			$apiClient->get('foo/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]));
344
			$this->fail();
345
346
		} catch (InvalidSignatureException $e) {
347
			$responseData = $response->getData();
348
			unset($responseData['signature']);
349
			$this->assertSame($responseData, $e->getResponseData());
350
		}
351
	}
352
353
	public function testCreateResponseByData()
354
	{
355
		$data = [
356
			'signature' => 'abc',
357
			'foo' => 123,
358
			'bar' => 456,
359
		];
360
361
		$cryptoService = $this->getMockBuilder(CryptoService::class)
362
			->disableOriginalConstructor()
363
			->getMock();
364
365
		$cryptoService->expects(self::any())
366
			->method('verifyData')
367
			->willReturn(true);
368
369
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
370
			->getMock();
371
372
		/** @var CryptoService $cryptoService */
373
		/** @var ApiClientDriver $apiClientDriver */
374
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
375
376
		$response = $apiClient->createResponseByData($data, new SignatureDataFormatter([]));
377
378
		unset($data['signature']);
379
380
		$this->assertInstanceOf(Response::class, $response);
381
		$this->assertSame(ResponseCode::S200_OK, $response->getResponseCode()->getValue());
382
		$this->assertEquals([], $response->getHeaders());
383
		$this->assertEquals($data, $response->getData());
384
	}
385
386
	public function testRequestWithExtension()
387
	{
388
		$cryptoService = $this->getMockBuilder(CryptoService::class)
389
			->disableOriginalConstructor()
390
			->getMock();
391
392
		$cryptoService->expects(self::once())
393
			->method('signData')
394
			->willReturn('signature');
395
396
		$cryptoService->expects(self::exactly(2))
397
			->method('verifyData')
398
			->willReturn(true);
399
400
		/** @var CryptoService $cryptoService */
401
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
402
			->getMock();
403
404
		$apiClientDriver->expects(self::once())
405
			->method('request')
406
			->willReturn(new Response(
407
				new ResponseCode(ResponseCode::S200_OK),
408
				['id' => '123', 'signature' => 'signature', 'extensions' => [['extension' => 'foo', 'foo' => 'bar', 'signature' => 'signatureExtension']]],
409
				[]
410
			));
411
412
		/** @var ApiClientDriver $apiClientDriver */
413
		$apiClient = new ApiClient($apiClientDriver, $cryptoService, self::API_URL);
414
415
		// @codingStandardsIgnoreStart
416
		$extensions = [
417
			'foo' => new class implements \SlevomatCsobGateway\Call\ResponseExtensionHandler {
418
419
				public function createResponse(array $decodeData): \stdClass
420
				{
421
					return (object) ['foo' => 'bar'];
422
				}
423
424
				public function getSignatureDataFormatter(): SignatureDataFormatter
425
				{
426
					return new SignatureDataFormatter([]);
427
				}
428
			},
429
		];
430
		// @codingStandardsIgnoreEnd
431
432
		$response = $apiClient->get('payment/status/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]), null, $extensions);
433
434
		$this->assertInstanceOf(Response::class, $response);
435
		$this->assertSame(ResponseCode::S200_OK, $response->getResponseCode()->getValue());
436
		$this->assertEquals([], $response->getHeaders());
437
		$this->assertEquals(['id' => '123'], $response->getData());
438
		$this->assertCount(1, $response->getExtensions());
439
		$this->assertInstanceOf(\stdClass::class, $response->getExtensions()['foo']);
440
		$this->assertSame('bar', $response->getExtensions()['foo']->foo);
441
	}
442
443
}
444