Issues (40)

Tests/ImplementationTest.php (6 issues)

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
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
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