Completed
Pull Request — master (#10)
by Jan
08:05 queued 05:12
created

ApiClientTest::testRequests()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 67
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 67
cc 6
eloc 45
nc 6
nop 8
rs 8.5896

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/' . date('YmdHis') . '/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/' . date('YmdHis') . '/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/' . date('YmdHis') . '/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
		self::markTestIncomplete('Contains imprecise DateTime handling');
112
		$currentTime = new \DateTimeImmutable();
113
114
		$cryptoService = $this->getMockBuilder(CryptoService::class)
115
			->disableOriginalConstructor()
116
			->getMock();
117
118
		$cryptoService->expects(self::any())
119
			->method('signData')
120
			->willReturn('signature');
121
122
		$cryptoService->expects(self::any())
123
			->method('verifyData')
124
			->willReturn(true);
125
126
		/** @var CryptoService $cryptoService */
127
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
128
			->getMock();
129
130
		if ($httpMethod->equalsValue(HttpMethod::GET)) {
131
			$apiClientDriver->expects(self::once())
132
				->method('request')
133
				->with($this->identicalTo($httpMethod), self::API_URL . '/' . $expectedUrl, $expectedRequestData)
134
				->willReturn(new Response(
135
					$responseCode,
136
					($responseData !== null ? $responseData : []) + [
137
						'signature' => 'signature',
138
					],
139
					$responseHeaders
140
				));
141
142
		} else {
143
			$apiClientDriver->expects(self::once())
144
				->method('request')
145
				->with($httpMethod, self::API_URL . '/' . $expectedUrl, $expectedRequestData + [
146
						'signature' => $cryptoService->signData($requestData, new SignatureDataFormatter([])),
147
						'dttm' => $currentTime->format('YmdHis'),
148
					])
149
				->willReturn(new Response(
150
					$responseCode,
151
					($responseData !== null ? $responseData : []) + [
152
						'signature' => 'signature',
153
					],
154
					$responseHeaders
155
				));
156
		}
157
158
		/** @var ApiClientDriver $apiClientDriver */
159
		$apiClient = new ApiClient($apiClientDriver, $cryptoService, self::API_URL);
160
161
		if ($httpMethod->equalsValue(HttpMethod::GET)) {
162
			$response = $apiClient->get($url, $requestData, new SignatureDataFormatter([]), new SignatureDataFormatter([]));
163
164
		} elseif ($httpMethod->equalsValue(HttpMethod::POST)) {
165
			$response = $apiClient->post($url, $requestData, new SignatureDataFormatter([]), new SignatureDataFormatter([]));
166
167
		} else {
168
			$response = $apiClient->put($url, $requestData, new SignatureDataFormatter([]), new SignatureDataFormatter([]));
169
		}
170
171
		$this->assertInstanceOf(Response::class, $response);
172
		$this->assertSame($responseCode->getValue(), $response->getResponseCode()->getValue());
173
		$this->assertEquals($responseHeaders, $response->getHeaders());
174
		$this->assertEquals($responseData, $response->getData());
175
	}
176
177
	public function getTestExceptions(): array
178
	{
179
		return [
180
			[
181
				new Response(
182
					new ResponseCode(ResponseCode::S400_BAD_REQUEST),
183
					[]
184
				),
185
				BadRequestException::class,
186
			],
187
			[
188
				new Response(
189
					new ResponseCode(ResponseCode::S403_FORBIDDEN),
190
					[]
191
				),
192
				ForbiddenException::class,
193
			],
194
			[
195
				new Response(
196
					new ResponseCode(ResponseCode::S404_NOT_FOUND),
197
					[]
198
				),
199
				NotFoundException::class,
200
			],
201
			[
202
				new Response(
203
					new ResponseCode(ResponseCode::S405_METHOD_NOT_ALLOWED),
204
					[]
205
				),
206
				MethodNotAllowedException::class,
207
			],
208
			[
209
				new Response(
210
					new ResponseCode(ResponseCode::S429_TOO_MANY_REQUESTS),
211
					[]
212
				),
213
				TooManyRequestsException::class,
214
			],
215
			[
216
				new Response(
217
					new ResponseCode(ResponseCode::S503_SERVICE_UNAVAILABLE),
218
					[]
219
				),
220
				ServiceUnavailableException::class,
221
			],
222
			[
223
				new Response(
224
					new ResponseCode(ResponseCode::S500_INTERNAL_ERROR),
225
					[]
226
				),
227
				InternalErrorException::class,
228
			],
229
		];
230
	}
231
232
	/**
233
	 * @param Response $response
234
	 * @param string $expectedExceptionClass
235
	 *
236
	 * @dataProvider getTestExceptions
237
	 */
238
	public function testExceptions(Response $response, string $expectedExceptionClass)
239
	{
240
		$cryptoService = $this->getMockBuilder(CryptoService::class)
241
			->disableOriginalConstructor()
242
			->getMock();
243
244
		$cryptoService->expects(self::once())
245
			->method('signData')
246
			->willReturn('signature');
247
248
		$cryptoService->expects(self::any())
249
			->method('verifyData')
250
			->willReturn(true);
251
252
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
253
			->getMock();
254
255
		$apiClientDriver->expects(self::once())
256
			->method('request')
257
			->willReturn($response);
258
259
		/** @var CryptoService $cryptoService */
260
		/** @var ApiClientDriver $apiClientDriver */
261
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
262
263
		try {
264
			$apiClient->get('foo/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]));
265
			$this->fail();
266
267
		} catch (RequestException $e) {
268
			$this->assertInstanceOf($expectedExceptionClass, $e);
269
			$this->assertSame($response, $e->getResponse());
270
		}
271
	}
272
273
	public function testMissingSignature()
274
	{
275
		$response = new Response(
276
			new ResponseCode(ResponseCode::S200_OK),
277
			[]
278
		);
279
280
		$cryptoService = $this->getMockBuilder(CryptoService::class)
281
			->disableOriginalConstructor()
282
			->getMock();
283
284
		$cryptoService->expects(self::once())
285
			->method('signData')
286
			->willReturn('signature');
287
288
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
289
			->getMock();
290
291
		$apiClientDriver->expects(self::once())
292
			->method('request')
293
			->willReturn($response);
294
295
		/** @var CryptoService $cryptoService */
296
		/** @var ApiClientDriver $apiClientDriver */
297
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
298
299
		try {
300
			$apiClient->get('foo/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]));
301
			$this->fail();
302
303
		} catch (InvalidSignatureException $e) {
304
			$this->assertSame($response->getData(), $e->getResponseData());
305
		}
306
	}
307
308
	public function testInvalidSignature()
309
	{
310
		$response = new Response(
311
			new ResponseCode(ResponseCode::S200_OK),
312
			[
313
				'signature' => 'invalidSignature',
314
			]
315
		);
316
317
		$cryptoService = $this->getMockBuilder(CryptoService::class)
318
			->disableOriginalConstructor()
319
			->getMock();
320
321
		$cryptoService->expects(self::once())
322
			->method('signData')
323
			->willReturn('signature');
324
325
		$cryptoService->expects(self::any())
326
			->method('verifyData')
327
			->willReturn(false);
328
329
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
330
			->getMock();
331
332
		$apiClientDriver->expects(self::once())
333
			->method('request')
334
			->willReturn($response);
335
336
		/** @var CryptoService $cryptoService */
337
		/** @var ApiClientDriver $apiClientDriver */
338
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
339
340
		try {
341
			$apiClient->get('foo/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]));
342
			$this->fail();
343
344
		} catch (InvalidSignatureException $e) {
345
			$responseData = $response->getData();
346
			unset($responseData['signature']);
347
			$this->assertSame($responseData, $e->getResponseData());
348
		}
349
	}
350
351
	public function testCreateResponseByData()
352
	{
353
		$data = [
354
			'signature' => 'abc',
355
			'foo' => 123,
356
			'bar' => 456,
357
		];
358
359
		$cryptoService = $this->getMockBuilder(CryptoService::class)
360
			->disableOriginalConstructor()
361
			->getMock();
362
363
		$cryptoService->expects(self::any())
364
			->method('verifyData')
365
			->willReturn(true);
366
367
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
368
			->getMock();
369
370
		/** @var CryptoService $cryptoService */
371
		/** @var ApiClientDriver $apiClientDriver */
372
		$apiClient = new ApiClient($apiClientDriver, $cryptoService);
373
374
		$response = $apiClient->createResponseByData($data, new SignatureDataFormatter([]));
375
376
		unset($data['signature']);
377
378
		$this->assertInstanceOf(Response::class, $response);
379
		$this->assertSame(ResponseCode::S200_OK, $response->getResponseCode()->getValue());
380
		$this->assertEquals([], $response->getHeaders());
381
		$this->assertEquals($data, $response->getData());
382
	}
383
384
	public function testRequestWithExtension()
385
	{
386
		$cryptoService = $this->getMockBuilder(CryptoService::class)
387
			->disableOriginalConstructor()
388
			->getMock();
389
390
		$cryptoService->expects(self::once())
391
			->method('signData')
392
			->willReturn('signature');
393
394
		$cryptoService->expects(self::exactly(2))
395
			->method('verifyData')
396
			->willReturn(true);
397
398
		/** @var CryptoService $cryptoService */
399
		$apiClientDriver = $this->getMockBuilder(ApiClientDriver::class)
400
			->getMock();
401
402
		$apiClientDriver->expects(self::once())
403
			->method('request')
404
			->willReturn(new Response(
405
				new ResponseCode(ResponseCode::S200_OK),
406
				['id' => '123', 'signature' => 'signature', 'extensions' => [['extension' => 'foo', 'foo' => 'bar', 'signature' => 'signatureExtension']]],
407
				[]
408
			));
409
410
		/** @var ApiClientDriver $apiClientDriver */
411
		$apiClient = new ApiClient($apiClientDriver, $cryptoService, self::API_URL);
412
413
		// @codingStandardsIgnoreStart
414
		$extensions = [
415
			'foo' => new class implements \SlevomatCsobGateway\Call\ResponseExtensionHandler {
416
417
				public function createResponse(array $decodeData): \stdClass
418
				{
419
					return (object) ['foo' => 'bar'];
420
				}
421
422
				public function getSignatureDataFormatter(): SignatureDataFormatter
423
				{
424
					return new SignatureDataFormatter([]);
425
				}
426
			},
427
		];
428
		// @codingStandardsIgnoreEnd
429
430
		$response = $apiClient->get('payment/status/{dttm}/{signature}', [], new SignatureDataFormatter([]), new SignatureDataFormatter([]), null, $extensions);
431
432
		$this->assertInstanceOf(Response::class, $response);
433
		$this->assertSame(ResponseCode::S200_OK, $response->getResponseCode()->getValue());
434
		$this->assertEquals([], $response->getHeaders());
435
		$this->assertEquals(['id' => '123'], $response->getData());
436
		$this->assertCount(1, $response->getExtensions());
437
		$this->assertInstanceOf(\stdClass::class, $response->getExtensions()['foo']);
438
		$this->assertSame('bar', $response->getExtensions()['foo']->foo);
439
	}
440
441
}
442