ImplementationTest::testThingsFail()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 4
nop 9
dl 0
loc 27
rs 9.8666
c 0
b 0
f 0

How to fix   Many Parameters   

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
2
/**
3
* @author SignpostMarv
4
*/
5
declare(strict_types=1);
6
7
namespace SignpostMarv\DaftFramework\Tests;
8
9
use BadMethodCallException;
10
use Generator;
11
use InvalidArgumentException;
12
use PHPUnit\Framework\TestCase as Base;
13
use SignpostMarv\DaftFramework\Framework;
14
use SignpostMarv\DaftFramework\HttpHandler;
15
use SignpostMarv\DaftRouter\DaftSource;
16
use SignpostMarv\DaftRouter\Tests\Fixtures\Config as DaftRouterFixturesConfig;
17
use Symfony\Component\HttpFoundation\Request;
18
use Throwable;
19
20
class ImplementationTest extends Base
21
{
22
	public function tearDown() : void
23
	{
24
		/**
25
		* @var string[]
26
		*/
27
		$strings = array_filter(
28
			[
29
				realpath(__DIR__ . '/fixtures/http-kernel.fast-route.cache'),
30
			],
31
			'is_string'
32
		);
33
34
		foreach (
35
			array_filter(
36
				$strings,
37
				'is_file'
38
			) as $cleanup
39
		) {
40
			unlink($cleanup);
41
		}
42
	}
43
44
	/**
45
	* @psalm-return Generator<int, array{0:class-string<Framework>, 1:array<string, array<int, mixed>>, 2:string, 3:string, 4:array}, mixed, void>
46
	*/
47
	public function DataProviderGoodSources() : Generator
48
	{
49
		yield from [
50
			[
51
				Framework::class,
52
				[],
53
				'https://example.com/',
54
				realpath(__DIR__ . '/fixtures'),
55
				[],
56
			],
57
			[
58
				Framework::class,
59
				[],
60
				'https://example.com:8080/',
61
				realpath(__DIR__ . '/fixtures'),
62
				[],
63
			],
64
			[
65
				Framework::class,
66
				[
67
					'ConfigureDatabaseConnection' => [
68
						'sqlite::memory:',
69
						null,
70
						null,
71
						[],
72
					],
73
				],
74
				'https://example.com/',
75
				realpath(__DIR__ . '/fixtures'),
76
				[],
77
			],
78
			[
79
				HttpHandler::class,
80
				[
81
					'ConfigureDatabaseConnection' => [
82
						'sqlite::memory:',
83
						null,
84
						null,
85
						[],
86
					],
87
				],
88
				'https://example.com/',
89
				realpath(__DIR__ . '/fixtures'),
90
				[
91
					DaftSource::class => [
92
						'cacheFile' => (__DIR__ . '/fixtures/http-kernel.fast-route.cache'),
93
						'sources' => [
94
							DaftRouterFixturesConfig::class,
95
						],
96
					],
97
				],
98
			],
99
		];
100
101
		if ((bool) getenv('SCRUTINIZER')) {
102
			yield [
103
				Framework::class,
104
				[
105
					'ConfigureDatabaseConnection' => [
106
						'mysql:host=localhost;dbname=information_schema',
107
						'root',
108
						'',
109
						[],
110
					],
111
				],
112
				'https://example.com/',
113
				realpath(__DIR__ . '/fixtures'),
114
				[],
115
			];
116
117
			yield [
118
				HttpHandler::class,
119
				[
120
					'ConfigureDatabaseConnection' => [
121
						'mysql:host=localhost;dbname=information_schema',
122
						'root',
123
						'',
124
						[],
125
					],
126
				],
127
				'https://example.com/',
128
				realpath(__DIR__ . '/fixtures'),
129
				[
130
					DaftSource::class => [
131
						'cacheFile' => (__DIR__ . '/fixtures/http-kernel.fast-route.cache'),
132
						'sources' => [
133
							DaftRouterFixturesConfig::class,
134
						],
135
					],
136
				],
137
			];
138
		}
139
	}
140
141
	/**
142
	* @psalm-return Generator<int, array{0:class-string<Framework>, 1:class-string<Throwable>, 2:string, 3:int|null, 4:array<string, array<int, mixed>>, 5:string, 6:string, 7:array}, mixed, void>
143
	*/
144
	public function DataProviderBadSources() : Generator
145
	{
146
		yield from [
147
			[
148
				Framework::class,
149
				InvalidArgumentException::class,
150
				'Base path must be a directory!',
151
				null,
152
				[],
153
				'https://example.com/',
154
				__FILE__,
155
				[],
156
			],
157
			[
158
				Framework::class,
159
				InvalidArgumentException::class,
160
				'Path should be explicitly set to via realpath!',
161
				null,
162
				[],
163
				'https://example.com/',
164
				(__DIR__ . '/fixtures/'),
165
				[],
166
			],
167
			[
168
				HttpHandler::class,
169
				InvalidArgumentException::class,
170
				sprintf(
171
					'%s config not found!',
172
					DaftSource::class
173
				),
174
				null,
175
				[],
176
				'https://example.com/',
177
				realpath(__DIR__ . '/fixtures'),
178
				[],
179
			],
180
			[
181
				HttpHandler::class,
182
				InvalidArgumentException::class,
183
				sprintf(
184
					'%s config does not specify "%s" correctly.',
185
					DaftSource::class,
186
					'cacheFile'
187
				),
188
				null,
189
				[],
190
				'https://example.com/',
191
				realpath(__DIR__ . '/fixtures'),
192
				[
193
					DaftSource::class => [
194
						'cacheFile' => false,
195
						'sources' => false,
196
					],
197
				],
198
			],
199
			[
200
				HttpHandler::class,
201
				InvalidArgumentException::class,
202
				sprintf(
203
					'%s config does not specify "%s" correctly.',
204
					DaftSource::class,
205
					'sources'
206
				),
207
				null,
208
				[],
209
				'https://example.com/',
210
				realpath(__DIR__ . '/fixtures'),
211
				[
212
					DaftSource::class => [
213
						'cacheFile' => (__DIR__ . '/fixtures/http-kernel.fast-route.cache'),
214
						'sources' => false,
215
					],
216
				],
217
			],
218
			[
219
				HttpHandler::class,
220
				InvalidArgumentException::class,
221
				sprintf(
222
					'%s config property cacheFile does not exist under the framework base path.',
223
					DaftSource::class
224
				),
225
				null,
226
				[],
227
				'https://example.com/',
228
				realpath(__DIR__ . '/fixtures'),
229
				[
230
					DaftSource::class => [
231
						'cacheFile' => __FILE__,
232
						'sources' => [],
233
					],
234
				],
235
			],
236
		];
237
	}
238
239
	/**
240
	* @psalm-return Generator<int, array{0:class-string<Framework>, 1:array<string, array<int, mixed>>, 2:string, 3:string, 4:array}, mixed, void>
241
	*/
242
	public function DataProviderGoodSourcesSansDatabaseConnection() : Generator
243
	{
244
		foreach ($this->DataProviderGoodSources() as $args) {
245
			if ( ! isset($args[1]['ConfigureDatabaseConnection'])) {
246
				yield $args;
247
			}
248
		}
249
	}
250
251
	/**
252
	* @psalm-return Generator<int, array{0:class-string<Framework>, 1:array<string, array<int, mixed>>, 2:string, 3:string, 4:array}, mixed, void>
253
	*/
254
	public function DataProviderGoodSourcesWithDatabaseConnection() : Generator
255
	{
256
		foreach ($this->DataProviderGoodSources() as $args) {
257
			if (isset($args[1]['ConfigureDatabaseConnection'])) {
258
				yield $args;
259
			}
260
		}
261
	}
262
263
	/**
264
	* @param array<string, array<int, mixed>> $postConstructionCalls
265
	* @param mixed ...$implementationArgs
266
	*
267
	* @dataProvider DataProviderGoodSources
268
	*/
269
	public function testEverythingInitialisesFine(
270
		string $implementation,
271
		array $postConstructionCalls,
272
		...$implementationArgs
273
	) : Framework {
274
		$instance = $this->ObtainFrameworkInstance($implementation, ...$implementationArgs);
275
		$this->ConfigureFrameworkInstance($instance, $postConstructionCalls);
276
277
		list($baseUrl, $basePath, $config) = $this->extractDefaultFrameworkArgs(
278
			$implementationArgs
279
		);
280
281
		static::assertSame($baseUrl, $instance->ObtainBaseUrl());
0 ignored issues
show
Bug Best Practice introduced by
The method PHPUnit\Framework\Assert::assertSame() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

281
		static::/** @scrutinizer ignore-call */ 
282
          assertSame($baseUrl, $instance->ObtainBaseUrl());
Loading history...
282
		static::assertSame($basePath, $instance->ObtainBasePath());
283
		static::assertSame($config, $instance->ObtainConfig());
284
285
		return $instance;
286
	}
287
288
	/**
289
	* @param class-string<Framework> $implementation
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Framework> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Framework>.
Loading history...
290
	* @param class-string<Throwable> $expectedExceptionClass
291
	* @param array<string, array<int, mixed>> $postConstructionCalls
292
	* @param mixed ...$implementationArgs
293
	*
294
	* @dataProvider DataProviderBadSources
295
	*
296
	* @depends testEverythingInitialisesFine
297
	*/
298
	public function testThingsFail(
299
		string $implementation,
300
		string $expectedExceptionClass,
301
		? string $expectedExceptionMessage,
302
		? int $expectedExceptionCode,
303
		array $postConstructionCalls,
304
		string $baseUrl,
305
		string $basePath,
306
		array $config,
307
		...$implementationArgs
308
	) : void {
309
		$this->expectException($expectedExceptionClass);
310
		if (is_string($expectedExceptionMessage)) {
311
			$this->expectExceptionMessage($expectedExceptionMessage);
0 ignored issues
show
Bug introduced by
The method expectExceptionMessage() does not exist on SignpostMarv\DaftFramewo...ests\ImplementationTest. Did you maybe mean expectException()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

311
			$this->/** @scrutinizer ignore-call */ 
312
          expectExceptionMessage($expectedExceptionMessage);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
312
		}
313
		if (is_int($expectedExceptionCode)) {
314
			$this->expectExceptionCode($expectedExceptionCode);
0 ignored issues
show
Bug introduced by
The method expectExceptionCode() does not exist on SignpostMarv\DaftFramewo...ests\ImplementationTest. Did you maybe mean expectException()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

314
			$this->/** @scrutinizer ignore-call */ 
315
          expectExceptionCode($expectedExceptionCode);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
315
		}
316
317
		$instance = $this->ObtainFrameworkInstance(
318
			$implementation,
319
			$baseUrl,
320
			$basePath,
321
			$config,
322
			...$implementationArgs
323
		);
324
		$this->ConfigureFrameworkInstance($instance, $postConstructionCalls);
325
	}
326
327
	/**
328
	* @param array<string, array<int, mixed>> $postConstructionCalls
329
	* @param mixed ...$implementationArgs
330
	*
331
	* @dataProvider DataProviderGoodSourcesSansDatabaseConnection
332
	*
333
	* @depends testEverythingInitialisesFine
334
	*/
335
	public function testGoodSourcesSansDatabaseConnection(
336
		string $implementation,
337
		array $postConstructionCalls,
338
		...$implementationArgs
339
	) : void {
340
		$instance = $this->ObtainFrameworkInstance($implementation, ...$implementationArgs);
341
		$this->ConfigureFrameworkInstance($instance, $postConstructionCalls);
342
343
		$this->expectException(BadMethodCallException::class);
344
		$this->expectExceptionMessage('Database Connection not available!');
345
346
		$instance->ObtainDatabaseConnection();
347
	}
348
349
	/**
350
	* @param array<string, array<int, mixed>> $postConstructionCalls
351
	* @param mixed ...$implementationArgs
352
	*
353
	* @dataProvider DataProviderGoodSourcesWithDatabaseConnection
354
	*
355
	* @depends testEverythingInitialisesFine
356
	*/
357
	public function testGoodSourcesWithDatabaseConnection(
358
		string $implementation,
359
		array $postConstructionCalls,
360
		...$implementationArgs
361
	) : void {
362
		$instance = $this->ObtainFrameworkInstance($implementation, ...$implementationArgs);
363
		$this->ConfigureFrameworkInstance($instance, $postConstructionCalls);
364
365
		$instance->ObtainDatabaseConnection();
366
367
		$this->expectException(BadMethodCallException::class);
368
		$this->expectExceptionMessage('Database Connection already made!');
369
370
		/**
371
		* @var array<int, string|array|null>
372
		* @var string $configureArgs[0]
373
		* @var string|null $configureArgs[1]
374
		* @var string|null $configureArgs[2]
375
		* @var array $configureArgs[3]
376
		*/
377
		$configureArgs = $postConstructionCalls['ConfigureDatabaseConnection'];
378
379
		$instance->ConfigureDatabaseConnection(
380
			$configureArgs[0],
381
			$configureArgs[1] ?? null,
382
			$configureArgs[2] ?? null,
383
			$configureArgs[3] ?? []
384
		);
385
	}
386
387
	/**
388
	* @dataProvider DataProviderGoodSources
389
	*
390
	* @depends testEverythingInitialisesFine
391
	*/
392
	public function testUnpairedFrameworksFail(string $implementation) : void
393
	{
394
		if ( ! is_a($implementation, Framework::class, true)) {
395
			static::assertTrue(is_a($implementation, Framework::class, true));
396
397
			return;
398
		}
399
400
		static::assertFalse(Request::createFromGlobals() === Request::createFromGlobals());
401
402
		$this->expectException(InvalidArgumentException::class);
403
		$this->expectExceptionMessage(
404
			'No framework instance has been paired with the provided request!'
405
		);
406
407
		$implementation::ObtainFrameworkForRequest(Request::createFromGlobals());
408
	}
409
410
	/**
411
	* @param array<string, array<int, mixed>> $postConstructionCalls
412
	* @param mixed ...$implementationArgs
413
	*
414
	* @dataProvider DataProviderGoodSources
415
	*
416
	* @depends testEverythingInitialisesFine
417
	* @depends testUnpairedFrameworksFail
418
	*/
419
	public function testDisposeOfFrameworkReferences(
420
		string $implementation,
421
		array $postConstructionCalls,
422
		...$implementationArgs
423
	) : void {
424
		if ( ! is_a($implementation, Framework::class, true)) {
425
			throw new InvalidArgumentException(sprintf(
426
				'Argument 1 passed to %s must be an implementation of %s, %s given!',
427
				__METHOD__,
428
				Framework::class,
429
				$implementation
430
			));
431
		}
432
433
		list($instance, $requestA) = $this->PrepareReferenceDisposalTest(
434
			$implementation,
435
			$postConstructionCalls,
436
			Request::createFromGlobals(),
437
			Request::createFromGlobals(),
438
			...$implementationArgs
439
		);
440
441
		$implementation::DisposeOfFrameworkReferences($instance);
442
443
		$this->expectException(InvalidArgumentException::class);
444
		$this->expectExceptionMessage(
445
			'No framework instance has been paired with the provided request!'
446
		);
447
448
		$implementation::ObtainFrameworkForRequest($requestA);
449
	}
450
451
	/**
452
	* @param array<string, array<int, mixed>> $postConstructionCalls
453
	* @param mixed ...$implementationArgs
454
	*
455
	* @dataProvider DataProviderGoodSources
456
	*
457
	* @depends testEverythingInitialisesFine
458
	* @depends testUnpairedFrameworksFail
459
	*/
460
	public function testDisposeOfRequestReferences(
461
		string $implementation,
462
		array $postConstructionCalls,
463
		...$implementationArgs
464
	) : void {
465
		if ( ! is_a($implementation, Framework::class, true)) {
466
			throw new InvalidArgumentException(sprintf(
467
				'Argument 1 passed to %s must be an implementation of %s, %s given!',
468
				__METHOD__,
469
				Framework::class,
470
				$implementation
471
			));
472
		}
473
474
		list($instance, $requestA, $requestB) = $this->PrepareReferenceDisposalTest(
475
			$implementation,
476
			$postConstructionCalls,
477
			Request::createFromGlobals(),
478
			Request::createFromGlobals(),
479
			...$implementationArgs
480
		);
481
482
		$implementation::DisposeOfFrameworkReferences($instance);
483
		$implementation::DisposeOfRequestReferences($requestA);
484
		$implementation::PairWithRequest($instance, $requestB);
485
486
		static::assertSame($instance, $implementation::ObtainFrameworkForRequest($requestB));
0 ignored issues
show
Bug Best Practice introduced by
The method PHPUnit\Framework\Assert::assertSame() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

486
		static::/** @scrutinizer ignore-call */ 
487
          assertSame($instance, $implementation::ObtainFrameworkForRequest($requestB));
Loading history...
487
488
		$implementation::DisposeOfRequestReferences($requestB);
489
490
		$this->expectException(InvalidArgumentException::class);
491
		$this->expectExceptionMessage(
492
			'No framework instance has been paired with the provided request!'
493
		);
494
495
		$implementation::ObtainFrameworkForRequest($requestB);
496
	}
497
498
	/**
499
	* @dataProvider DataProviderGoodSources
500
	*/
501
	public function testNormaliseUrlFails(string $implementation) : void
502
	{
503
		if ( ! is_a($implementation, Framework::class, true)) {
504
			throw new InvalidArgumentException(sprintf(
505
				'Argument 1 passed to %s must be an implementation of %s, %s given!',
506
				__METHOD__,
507
				Framework::class,
508
				$implementation
509
			));
510
		}
511
512
		$this->expectException(InvalidArgumentException::class);
513
		$this->expectExceptionMessage(
514
			'Base URL must have at least a scheme, host & path in order to be normalised!'
515
		);
516
517
		$implementation::NormaliseUrl('');
518
	}
519
520
	protected function extractDefaultFrameworkArgs(array $implementationArgs) : array
521
	{
522
		list($baseUrl, $basePath, $config) = $implementationArgs;
523
524
		return [$baseUrl, $basePath, $config];
525
	}
526
527
	/**
528
	* @param array<string, mixed[]> $postConstructionCalls
529
	* @param mixed ...$implementationArgs
530
	*
531
	* @psalm-return array{0:Framework, 1:Request, 2:Request}
532
	*/
533
	protected function PrepareReferenceDisposalTest(
534
		string $implementation,
535
		array $postConstructionCalls,
536
		Request $requestA,
537
		Request $requestB,
538
		...$implementationArgs
539
	) : array {
540
		if ( ! is_a($implementation, Framework::class, true)) {
541
			throw new InvalidArgumentException(sprintf(
542
				'Argument 1 passed to %s must be an implementation of %s, %s given!',
543
				__METHOD__,
544
				Framework::class,
545
				$implementation
546
			));
547
		}
548
549
		$instance = $this->ObtainFrameworkInstance($implementation, ...$implementationArgs);
550
		$this->ConfigureFrameworkInstance($instance, $postConstructionCalls);
551
552
		$implementation::PairWithRequest($instance, $requestA);
553
		$implementation::PairWithRequest($instance, $requestB);
554
555
		static::assertSame($instance, $implementation::ObtainFrameworkForRequest($requestA));
0 ignored issues
show
Bug Best Practice introduced by
The method PHPUnit\Framework\Assert::assertSame() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

555
		static::/** @scrutinizer ignore-call */ 
556
          assertSame($instance, $implementation::ObtainFrameworkForRequest($requestA));
Loading history...
556
		static::assertSame($instance, $implementation::ObtainFrameworkForRequest($requestB));
557
558
		return [$instance, $requestA, $requestB];
559
	}
560
561
	/**
562
	* @param mixed ...$implementationArgs
563
	*/
564
	protected function ObtainFrameworkInstance(string $implementation, ...$implementationArgs) : Framework
565
	{
566
		return Utilities::ObtainFrameworkInstanceMixedArgs(
567
			$this,
568
			$implementation,
569
			...$implementationArgs
570
		);
571
	}
572
573
	/**
574
	* @param array<string, mixed[]> $postConstructionCalls
575
	*/
576
	protected function ConfigureFrameworkInstance(
577
		Framework $instance,
578
		array $postConstructionCalls
579
	) : void {
580
		Utilities::ConfigureFrameworkInstance($this, $instance, $postConstructionCalls);
581
	}
582
}
583