ApiClientTest::testRequests()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 75
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

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