Completed
Push — master ( 8532b6...233c5a )
by Jan
02:42
created

ApiClientTest.php$0 ➔ createResponse()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

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