ldc-arcadia-genesis-php-client/tests/Architecture/ArchTest.php

266 lines
8.4 KiB
PHP

<?php
declare(strict_types=1);
use Uwil\ArcadiaApiPhpClient\Auth\ApiMandatoryHeadersMiddleware;
use Uwil\ArcadiaApiPhpClient\Auth\ClientIdentifier;
use Uwil\ArcadiaApiPhpClient\Auth\ClientSecret;
use Uwil\ArcadiaApiPhpClient\Auth\Nonce;
use Uwil\ArcadiaApiPhpClient\Error\AbstractApiError;
use Uwil\ArcadiaApiPhpClient\Error\ApiError;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\Inco\PortionType;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoCompo\AbstractIncoCompoPartDto;
use Uwil\ArcadiaApiPhpClient\Lib\ApiDto\Dto\IncoCompo\IncoCompoPartDto;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\EmployeeSortColumn;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\IncoSortColumn;
use Uwil\ArcadiaApiPhpClient\Lib\ApiSorter\SortColumn;
use Uwil\ArcadiaApiPhpClient\Lib\LibMiddleware;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\PageNumber;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\PageSize;
use Uwil\ArcadiaApiPhpClient\Lib\Pagination\Pagination;
use Uwil\ArcadiaApiPhpClient\Lib\ValueObjectId;
use Uwil\ArcadiaApiPhpClient\Logger\ApiLoggerMiddleware;
use Uwil\ArcadiaApiPhpClient\Repository\AbstractApiRepository;
use Uwil\ArcadiaApiPhpClient\Repository\Repository;
$libraryNamespace = 'Uwil\ArcadiaApiPhpClient';
describe('coding standards', function () use ($libraryNamespace): void {
arch()->preset()->php();
arch()->preset()->security();
test('no shell execution functions are used')
->expect(['exec', 'shell_exec', 'system', 'passthru', 'proc_open', 'popen', 'pcntl_exec'])
->not->toBeUsed();
test('no eval usage')
->expect(['eval'])
->not->toBeUsed();
test('no dangerous file functions')
->expect(['file_put_contents'])
->not->toBeUsed();
test('no globals are used')
->expect(['$GLOBALS', 'global'])
->not->toBeUsed();
test('no singletons')
->expect(['getInstance', 'instance'])
->not->toBeUsed();
test('no insecure hashing')
->expect(['md5', 'sha1'])
->not->toBeUsed();
test('no sleep function are used')
->expect(['sleep', 'usleep', 'time_nanosleep', 'time_sleep_until'])
->not->toBeUsed();
test('no debug globals are used')
->expect(['var_dump', 'die', 'print_r', 'dd', 'dump'])
->not->toBeUsed();
arch('all source files use strict types')
->expect($libraryNamespace)
->toUseStrictTypes();
});
describe('naming conventions', function () use ($libraryNamespace): void {
arch('mappers have Mapper suffix')
->expect("{$libraryNamespace}\Mapper")
->toHaveSuffix('Mapper');
arch('repositories have Repository suffix')
->expect("{$libraryNamespace}\Repository")
->toHaveSuffix('Repository');
arch('services have Service suffix')
->expect("{$libraryNamespace}\Service")
->toHaveSuffix('Service');
arch('errors have Error suffix')
->expect("{$libraryNamespace}\Error")
->toHaveSuffix('Error');
arch('factories have Factory suffix')
->expect("{$libraryNamespace}\Factory")
->toHaveSuffix('Factory');
arch('middlewares have Middleware suffix')
->expect([
ApiMandatoryHeadersMiddleware::class,
ApiLoggerMiddleware::class,
])
->toHaveSuffix('Middleware');
});
describe('inheritance contracts', function () use ($libraryNamespace): void {
arch('ApiDtos implement Dto')
->expect("{$libraryNamespace}\Lib\ApiDto\Dto")
->toImplement(Dto::class)
->ignoring([
Dto::class,
AbstractIncoCompoPartDto::class,
IncoCompoPartDto::class,
]);
arch('Errors implement ApiError')
->expect("{$libraryNamespace}\Error")
->toImplement(ApiError::class)
->ignoring([ApiError::class]);
arch('Concrete errors extend AbstractApiError')
->expect("{$libraryNamespace}\Error")
->classes()
->toExtend(AbstractApiError::class)
->ignoring([AbstractApiError::class]);
arch('Repositories implement Repository')
->expect("{$libraryNamespace}\Repository")
->toImplement(Repository::class)
->ignoring([Repository::class]);
arch('Concrete repositories extend AbstractApiRepository')
->expect("{$libraryNamespace}\Repository")
->classes()
->toExtend(AbstractApiRepository::class)
->ignoring([AbstractApiRepository::class]);
arch('Middlewares implement LibMiddleware')
->expect([
ApiMandatoryHeadersMiddleware::class,
ApiLoggerMiddleware::class,
])
->toImplement(LibMiddleware::class);
arch('Sort columns implement SortColumn')
->expect([
EmployeeSortColumn::class,
IncoSortColumn::class,
])
->toImplement(SortColumn::class);
arch('Value objects implement ValueObjectId')
->expect("{$libraryNamespace}\Lib\ApiValueObject")
->toImplement(ValueObjectId::class);
});
describe('class modifiers and immutability', function () use ($libraryNamespace): void {
arch('Api DTOs are final and readonly')
->expect("{$libraryNamespace}\Lib\ApiDto\Dto")
->classes()
->toBeReadonly()
->toBeFinal()
->ignoring([
Dto::class,
AbstractIncoCompoPartDto::class,
IncoCompoPartDto::class,
PortionType::class,
]);
arch('Value objects are final and readonly')
->expect("{$libraryNamespace}\Lib\ApiValueObject")
->classes()
->toBeReadonly()
->toBeFinal();
arch('Auth value objects are final and readonly')
->expect([
ClientIdentifier::class,
ClientSecret::class,
Nonce::class,
])
->classes()
->toBeReadonly()
->toBeFinal();
arch('Concrete errors are final and readonly')
->expect("{$libraryNamespace}\Error")
->classes()
->toBeReadonly()
->toBeFinal()
->ignoring([
ApiError::class,
AbstractApiError::class,
]);
arch('Factories are final')
->expect("{$libraryNamespace}\Factory")
->classes()
->toBeFinal();
arch('Concrete services are final')
->expect("{$libraryNamespace}\Service")
->classes()
->toBeFinal();
arch('Concrete repositories are final')
->expect("{$libraryNamespace}\Repository")
->classes()
->toBeFinal()
->ignoring([AbstractApiRepository::class]);
arch('Middlewares are final')
->expect([
ApiMandatoryHeadersMiddleware::class,
ApiLoggerMiddleware::class,
])
->classes()
->toBeFinal();
arch('Pagination Value objects are final and readonly')
->expect([
PageNumber::class,
PageSize::class,
])
->classes()
->toBeReadonly()
->toBeFinal();
arch('Pagination is final')
->expect(Pagination::class)
->toBeFinal();
});
describe('layer isolation', function () use ($libraryNamespace): void {
arch('DTOs do not depend on Repositories, Services, or Mappers')
->expect("{$libraryNamespace}\Lib\ApiDto")
->not->toUse([
"{$libraryNamespace}\Repository",
"{$libraryNamespace}\Service",
"{$libraryNamespace}\Mapper",
]);
arch('Value objects do not depend on Repositories, Services, Mappers, or DTOs')
->expect("{$libraryNamespace}\Lib\ApiValueObject")
->not->toUse([
"{$libraryNamespace}\Repository",
"{$libraryNamespace}\Service",
"{$libraryNamespace}\Mapper",
"{$libraryNamespace}\Lib\ApiDto",
]);
arch('Errors do not depend on Repositories, Services, Mappers, or DTOs')
->expect("{$libraryNamespace}\Error")
->not->toUse([
"{$libraryNamespace}\Repository",
"{$libraryNamespace}\Service",
"{$libraryNamespace}\Mapper",
"{$libraryNamespace}\Lib\ApiDto",
]);
arch('Repositories do not depend on Services')
->expect("{$libraryNamespace}\Repository")
->not->toUse([
"{$libraryNamespace}\Service",
]);
arch('Mappers do not depend on Repositories or Services')
->expect("{$libraryNamespace}\Mapper")
->not->toUse([
"{$libraryNamespace}\Repository",
"{$libraryNamespace}\Service",
]);
});