first commit
This commit is contained in:
93
tests/Architecture/ApplicationWorkflowShapeTest.php
Normal file
93
tests/Architecture/ApplicationWorkflowShapeTest.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use Tests\Architecture\Support\ArchitectureTestCase;
|
||||
|
||||
final class ApplicationWorkflowShapeTest extends ArchitectureTestCase
|
||||
{
|
||||
public function testUseCasesFollowTheProjectShapeConvention(): void
|
||||
{
|
||||
$files = $this->phpFilesUnder('src/*/Application/UseCase');
|
||||
self::assertNotSame([], $files, 'Expected at least one use case file.');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$class = $this->reflectProjectClass($file);
|
||||
$basename = pathinfo($file, PATHINFO_FILENAME);
|
||||
|
||||
self::assertSame(
|
||||
$basename,
|
||||
$class->getShortName(),
|
||||
$this->fileRuleMessage('Use cases must be named after their file', $file),
|
||||
);
|
||||
self::assertTrue($class->isFinal(), $this->fileRuleMessage('Use cases must be final classes', $file));
|
||||
self::assertTrue($class->isReadOnly(), $this->fileRuleMessage('Use cases must be readonly classes', $file));
|
||||
|
||||
$publicMethods = array_values(array_map(
|
||||
static fn (ReflectionMethod $method): string => $method->getName(),
|
||||
array_filter(
|
||||
$class->getMethods(ReflectionMethod::IS_PUBLIC),
|
||||
static fn (ReflectionMethod $method): bool => $method->getDeclaringClass()->getName() === $class->getName()
|
||||
&& !$method->isConstructor()
|
||||
&& !$method->isDestructor(),
|
||||
),
|
||||
));
|
||||
sort($publicMethods);
|
||||
|
||||
self::assertSame(
|
||||
['handle'],
|
||||
$publicMethods,
|
||||
$this->fileRuleMessage('Use cases must expose a single public handle(...) entrypoint', $file),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCommandsRemainImmutableInputDtos(): void
|
||||
{
|
||||
$files = $this->phpFilesUnder('src/*/Application/Command');
|
||||
self::assertNotSame([], $files, 'Expected at least one command file.');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$basename = pathinfo($file, PATHINFO_FILENAME);
|
||||
$class = $this->reflectProjectClass($file);
|
||||
|
||||
self::assertStringEndsWith('Command', $basename, $this->fileRuleMessage('Command files must end with Command', $file));
|
||||
self::assertSame(
|
||||
$basename,
|
||||
$class->getShortName(),
|
||||
$this->fileRuleMessage('Commands must be named after their file', $file),
|
||||
);
|
||||
self::assertTrue($class->isFinal(), $this->fileRuleMessage('Commands must be final classes', $file));
|
||||
self::assertTrue($class->isReadOnly(), $this->fileRuleMessage('Commands must be readonly classes', $file));
|
||||
}
|
||||
}
|
||||
|
||||
public function testUiLayerDoesNotReferenceUseCasesOrCommandsDirectly(): void
|
||||
{
|
||||
$files = $this->uiFiles();
|
||||
self::assertNotSame([], $files, 'Expected UI files to exist.');
|
||||
|
||||
$this->assertFilesDoNotReferenceFragments(
|
||||
$files,
|
||||
['\\Application\\UseCase\\', '\\Application\\Command\\'],
|
||||
'UI layer must enter a domain through its application service, not through use cases or commands',
|
||||
);
|
||||
}
|
||||
|
||||
private function reflectProjectClass(string $file): ReflectionClass
|
||||
{
|
||||
$relativePath = $this->relativePath($file);
|
||||
$className = 'Netig\Netslim\\' . str_replace(['/', '.php'], ['\\', ''], substr($relativePath, 4));
|
||||
|
||||
try {
|
||||
return new ReflectionClass($className);
|
||||
} catch (ReflectionException $exception) {
|
||||
self::fail($this->fileRuleMessage('Unable to reflect class ' . $className . ': ' . $exception->getMessage(), $file));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user