Passed
Pull Request — master (#38)
by Jan
04:37
created

ApiClientTest::testRequests()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 77
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 4
eloc 52
c 6
b 0
f 0
nc 6
nop 8
dl 0
loc 77
rs 9.0472

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