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

ApiClientTest   A

Complexity

Total Complexity 3

Size/Duplication

Total Lines 441
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
eloc 225
c 8
b 1
f 0
dl 0
loc 441
rs 10
wmc 3

11 Methods

Rating   Name   Duplication   Size   Complexity  
A testMissingSignature() 0 32 2
A hp$0 ➔ createResponse() 0 3 1
A testRequests() 0 77 4
A testExceptions() 0 32 2
A hp$0 ➔ getSignatureDataFormatter() 0 3 1
A testInvalidSignature() 0 40 2
A hp$0 ➔ testRequestWithExtension() 0 54 1
A getTestExceptions() 0 51 1
A testCreateResponseByData() 0 30 1
testRequestWithExtension() 0 54 ?
A getRequests() 0 79 1
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