ApiClientTest.php$0 ➔ createResponse()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
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