test(architecture): refactor architecture tests and Pest configuration
This commit is contained in:
parent
039557f95e
commit
4d922eb4db
6 changed files with 124 additions and 211 deletions
|
|
@ -2,30 +2,54 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
describe('application coding rules', function (): void {
|
||||
arch('no Domain Entities are used in Controllers')
|
||||
->expect('Application\\Http\\Controllers')
|
||||
->not->toUse('Domain\\Models\\Entities');
|
||||
use Shared\DDD\UseCases\Input;
|
||||
use Shared\DDD\UseCases\Output;
|
||||
use Shared\DDD\UseCases\UseCase;
|
||||
|
||||
arch('no Domain Entities are used in Policies')
|
||||
->expect('Application\\Policies')
|
||||
->not->toUse('Domain\\Models\\Entities');
|
||||
|
||||
arch('app Repositories implement Application\Contracts\Repository')
|
||||
->expect('Application\\Repositories')
|
||||
->toImplement('Application\\Contracts\\Repository');
|
||||
|
||||
arch('app Directories implement Application\Contracts\Directory')
|
||||
->expect('Application\\Directories')
|
||||
->toImplement('Application\\Contracts\\Directory');
|
||||
|
||||
arch('app DTOs are final readonly classes')
|
||||
->expect('Application\\Dto')
|
||||
describe('application layer architecture rules', function (): void {
|
||||
arch('use cases implement UseCase and are final')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->toBeFinal()
|
||||
->implementing(UseCase::class)
|
||||
->toBeFinal();
|
||||
|
||||
arch('use cases implement UseCase and are readonly')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->implementing(UseCase::class)
|
||||
->toBeReadonly();
|
||||
|
||||
arch('no debug calls in production code')
|
||||
->expect(['dd', 'dump', 'die', 'var_dump', 'sleep'])
|
||||
->not->toBeUsed();
|
||||
arch('use case inputs implement Input interface and are final')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->implementing(Input::class)
|
||||
->toBeFinal();
|
||||
|
||||
arch('use case inputs implement Input interface and are readonly')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->implementing(Input::class)
|
||||
->toBeReadonly();
|
||||
|
||||
arch('use case outputs implement Output interface and are final')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->implementing(Output::class)
|
||||
->toBeFinal();
|
||||
|
||||
arch('use case outputs implement Output interface and are readonly')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->implementing(Output::class)
|
||||
->toBeReadonly();
|
||||
|
||||
arch('application exceptions extend Exception and are final')
|
||||
->expect('Application')
|
||||
->classes()
|
||||
->extending(\Exception::class)
|
||||
->toBeFinal();
|
||||
|
||||
arch('application layer does not use Infrastructure or framework internals')
|
||||
->expect('Application')
|
||||
->not->toUse(['Infrastructure', 'Illuminate', 'Symfony']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,47 +6,19 @@ describe('coding standards', function (): void {
|
|||
arch()->preset()->php();
|
||||
arch()->preset()->security();
|
||||
|
||||
arch('it has no shell execution functions are used')
|
||||
->expect(['exec', 'shell_exec', 'system', 'passthru', 'proc_open'])
|
||||
->not->toBeUsed();
|
||||
arch('all source files use strict types')
|
||||
->expect(['Application', 'Domain', 'Shared'])
|
||||
->toUseStrictTypes();
|
||||
|
||||
arch('it has no eval usage')
|
||||
->expect(['eval'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('it has no dangerous file functions')
|
||||
->expect(['file_get_contents', 'file_put_contents', 'fopen'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('it has no globals are used')
|
||||
->expect(['$GLOBALS', 'global'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('it has no singletons')
|
||||
->expect(['getInstance', 'instance'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('it has no insecure hashing')
|
||||
->expect(['md5', 'sha1'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('it has no sleep function are used')
|
||||
arch('no sleep functions in production code')
|
||||
->expect(['sleep', 'usleep'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('it has no debug globals are used')
|
||||
->expect(['var_dump', 'die', 'print_r'])
|
||||
arch('no singletons in production code')
|
||||
->expect(['getInstance', 'instance'])
|
||||
->not->toBeUsed();
|
||||
|
||||
arch('all app source files use strict types')
|
||||
->expect('Application')
|
||||
->toUseStrictTypes();
|
||||
|
||||
arch('all domain source files use strict types')
|
||||
->expect('Domain')
|
||||
->toUseStrictTypes();
|
||||
|
||||
arch('all shared source files use strict types')
|
||||
->expect('Shared')
|
||||
->toUseStrictTypes();
|
||||
arch('no direct filesystem writes in domain or application')
|
||||
->expect(['Application', 'Domain'])
|
||||
->not->toUse(['file_put_contents', 'fopen']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,43 +5,39 @@ declare(strict_types=1);
|
|||
use Shared\DDD\Models\Entity;
|
||||
use Shared\DDD\Ports\Repository;
|
||||
|
||||
describe('domain coding rules', function (): void {
|
||||
arch('entities implements Entity')
|
||||
describe('domain layer architecture rules', function (): void {
|
||||
arch('entities implement Entity and are final readonly')
|
||||
->expect('Domain\\Models\\Entities')
|
||||
->toImplement(Entity::class);
|
||||
|
||||
arch('entities are immutable (disabled due to mock-final-classes conflict)')
|
||||
->expect('Domain\\Models\\Entities')
|
||||
->toBeReadonly()
|
||||
->toBeFinal();
|
||||
|
||||
arch('valueObjects are immutable (disabled due to mock-final-classes conflict)')
|
||||
->expect('Domain\\Models\\ValueObjects')
|
||||
->toBeReadonly()
|
||||
->toBeFinal();
|
||||
|
||||
arch('ports contains only interfaces')
|
||||
->expect('Domain\\Ports')
|
||||
->classes()
|
||||
->toBeInterface();
|
||||
->toImplement(Entity::class)
|
||||
->toBeFinal()
|
||||
->toBeReadonly();
|
||||
|
||||
arch('port Repositories extend Shared\DDD\Repository')
|
||||
arch('value objects classes are readonly')
|
||||
->expect('Domain\\Models\\ValueObjects')
|
||||
->classes()
|
||||
->toBeReadonly();
|
||||
|
||||
arch('value objects classes are final')
|
||||
->expect('Domain\\Models\\ValueObjects')
|
||||
->classes()
|
||||
->toBeFinal();
|
||||
|
||||
arch('domain exceptions extend DomainException and are final')
|
||||
->expect('Domain\\Exceptions')
|
||||
->classes()
|
||||
->toExtend(\DomainException::class)
|
||||
->toBeFinal();
|
||||
|
||||
arch('ports contain only interfaces')
|
||||
->expect('Domain\\Ports')
|
||||
->toBeInterfaces();
|
||||
|
||||
arch('repository ports extend Shared Repository')
|
||||
->expect('Domain\\Ports\\Repositories')
|
||||
->toExtend(Repository::class);
|
||||
|
||||
arch('no Application classes are used in Domain')
|
||||
arch('domain does not depend on external layers or frameworks')
|
||||
->expect('Domain')
|
||||
->not->toUse('Application');
|
||||
|
||||
arch('no Infrastructure classes are used in Domain')
|
||||
->expect('Domain')
|
||||
->not->toUse('Infrastructure');
|
||||
|
||||
arch('no Illuminate classes are used in Domain')
|
||||
->expect('Domain')
|
||||
->not->toUse('Illuminate');
|
||||
|
||||
arch('no Symfony components are used in Domain')
|
||||
->expect('Domain')
|
||||
->not->toUse('Symfony');
|
||||
->not->toUse(['Application', 'Infrastructure', 'Illuminate', 'Symfony']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,62 +2,23 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
describe('naming conventions in application', function (): void {
|
||||
arch('casts have Cast suffix')
|
||||
->expect('Application\\Cast')
|
||||
->toHaveSuffix('Cast');
|
||||
use Shared\DDD\UseCases\Input;
|
||||
|
||||
arch('adapters repositories have Repository suffix')
|
||||
->expect('Application\\Adapters\\Repositories')
|
||||
->toHaveSuffix('Repository');
|
||||
|
||||
arch('exceptions have Exception suffix')
|
||||
->expect('Application\\Exceptions')
|
||||
describe('naming conventions', function (): void {
|
||||
arch('all exceptions have Exception suffix')
|
||||
->expect(['Application', 'Domain', 'Shared'])
|
||||
->classes()
|
||||
->extending(\Throwable::class)
|
||||
->toHaveSuffix('Exception');
|
||||
|
||||
arch('controllers have Controller suffix')
|
||||
->expect('Application\\Http\\Controllers')
|
||||
->toHaveSuffix('Controller');
|
||||
|
||||
arch('requests have Request suffix')
|
||||
->expect('Application\\Http\\Requests')
|
||||
->toHaveSuffix('Request');
|
||||
|
||||
arch('resources have Resource suffix')
|
||||
->expect('Application\\Http\\Resources')
|
||||
->toHaveSuffix('Resource');
|
||||
|
||||
arch('policies have Policy suffix')
|
||||
->expect('Application\\Policies')
|
||||
->toHaveSuffix('Policy');
|
||||
|
||||
arch('providers have Provider suffix')
|
||||
->expect('Application\\Providers')
|
||||
->toHaveSuffix('Provider');
|
||||
|
||||
arch('services have Service suffix')
|
||||
->expect('Application\\Services')
|
||||
->toHaveSuffix('Service');
|
||||
});
|
||||
|
||||
describe('naming conventions in domain', function (): void {
|
||||
arch('dtos have Dto suffix')
|
||||
->expect('Domain\\Dto')
|
||||
->toHaveSuffix('Dto');
|
||||
|
||||
arch('exceptions have Exception suffix')
|
||||
->expect('Domain\\Exceptions')
|
||||
->toHaveSuffix('Exception');
|
||||
|
||||
arch('policies have Policy suffix')
|
||||
->expect('Domain\\Policies')
|
||||
->toHaveSuffix('Policy');
|
||||
|
||||
arch('port repositories have Repository suffix')
|
||||
arch('port repository interfaces have Repository suffix')
|
||||
->expect('Domain\\Ports\\Repositories')
|
||||
->interfaces()
|
||||
->toHaveSuffix('Repository');
|
||||
|
||||
arch('domain services have Service suffix')
|
||||
->expect('Domain\\Services')
|
||||
->toHaveSuffix('Service');
|
||||
arch('use case inputs are named Input')
|
||||
->expect('Application\\UseCases')
|
||||
->classes()
|
||||
->implementing(Input::class)
|
||||
->toHaveSuffix('Input');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,46 +2,31 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Shared\DDD\Models\Entity;
|
||||
use Shared\DDD\Ports\Repository;
|
||||
|
||||
describe('shared coding rules', function (): void {
|
||||
arch('entities implements Entity')
|
||||
->expect('Shared\\Domain\\Models\\Entities')
|
||||
->toImplement(Entity::class);
|
||||
|
||||
arch('entities are immutable (disabled due to mock-final-classes conflict)')
|
||||
->expect('Shared\\Entities')
|
||||
->toBeReadonly()
|
||||
->toBeFinal();
|
||||
|
||||
arch('valueObjects are immutable (disabled due to mock-final-classes conflict)')
|
||||
->expect('Shared\\Models\\ValueObjects')
|
||||
->toBeReadonly()
|
||||
->toBeFinal();
|
||||
|
||||
arch('ports contains only interfaces')
|
||||
->expect('Shared\\Ports')
|
||||
describe('shared layer architecture rules', function (): void {
|
||||
arch('shared value objects classes are final')
|
||||
->expect('Shared\\ValueObjects')
|
||||
->classes()
|
||||
->toBeInterface();
|
||||
->toBeFinal();
|
||||
|
||||
arch('port Repositories extend Shared\DDD\Repository')
|
||||
->expect('Shared\\Ports\\Repositories')
|
||||
->toExtend(Repository::class);
|
||||
arch('shared value objects classes are readonly')
|
||||
->expect('Shared\\ValueObjects')
|
||||
->classes()
|
||||
->toBeReadonly();
|
||||
|
||||
arch('no Application classes are used in Shared')
|
||||
arch('shared ports contain only interfaces')
|
||||
->expect('Shared\\DDD\\Ports')
|
||||
->toBeInterfaces();
|
||||
|
||||
arch('shared usecase contracts are interfaces')
|
||||
->expect('Shared\\DDD\\UseCases')
|
||||
->toBeInterfaces();
|
||||
|
||||
arch('shared exceptions extend Exception')
|
||||
->expect('Shared\\DDD\\Exceptions')
|
||||
->classes()
|
||||
->toExtend(\Exception::class);
|
||||
|
||||
arch('shared layer is completely isolated from other application layers')
|
||||
->expect('Shared')
|
||||
->not->toUse('Application');
|
||||
|
||||
arch('no Infrastructure classes are used in Shared')
|
||||
->expect('Shared')
|
||||
->not->toUse('Infrastructure');
|
||||
|
||||
arch('no Illuminate classes are used in Shared')
|
||||
->expect('Shared')
|
||||
->not->toUse('Illuminate');
|
||||
|
||||
arch('no Symfony components are used in Shared')
|
||||
->expect('Shared')
|
||||
->not->toUse('Symfony');
|
||||
->not->toUse(['Application', 'Domain', 'Infrastructure', 'Illuminate', 'Symfony']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,23 +5,11 @@ declare(strict_types=1);
|
|||
use DG\BypassFinals;
|
||||
use Tests\TestCase;
|
||||
|
||||
// Prevents mock-final-classes/bypass-finals from removing final/readonly.
|
||||
if (class_exists(BypassFinals::class)) {
|
||||
BypassFinals::denyPaths([
|
||||
dirname(__DIR__) . '/src/*',
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------------
|
||||
* Test Case
|
||||
*--------------------------------------------------------------------------
|
||||
*
|
||||
* The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||
* case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||
* need to change it using the "pest()" function to bind different classes or traits.
|
||||
*
|
||||
*/
|
||||
BypassFinals::enable(bypassReadOnly: false, bypassFinal: true);
|
||||
BypassFinals::denyPaths([
|
||||
'*/src/*',
|
||||
'*/tests/*',
|
||||
]);
|
||||
|
||||
pest()->extend(TestCase::class)
|
||||
->in('Architecture', 'Unit');
|
||||
|
|
@ -30,16 +18,3 @@ pest()->extend(TestCase::class)
|
|||
pest()->group('architecture')->in('Architecture');
|
||||
|
||||
pest()->group('unit')->in('Unit');
|
||||
|
||||
/*
|
||||
*--------------------------------------------------------------------------
|
||||
* Expectations
|
||||
*--------------------------------------------------------------------------
|
||||
*
|
||||
* When you're writing tests, you often need to check that values meet certain conditions. The
|
||||
* "expect()" function gives you access to a set of "expectations" methods that you can use
|
||||
* to assert different things. Of course, you may extend the Expectation API at any time.
|
||||
*
|
||||
*/
|
||||
|
||||
expect()->extend('toBeOne', fn () => $this->toBe(1));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue