Completed
Pull Request — master (#10)
by Jan
08:41 queued 05:59
created

ApiClientTest::testRequestWithExtension()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 56
Code Lines 32

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 56
rs 9.7251
cc 1
eloc 32
nc 1
nop 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A ApiClientTest.php$0 ➔ createResponse() 0 4 1
A ApiClientTest.php$0 ➔ getSignatureDataFormatter() 0 4 1

How to fix   Long Method   

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:

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