inti project
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class AttendanceBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_scanner_controller_delegates_and_does_not_calculate_status(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Http/Controllers/Api/V2/Attendance', ['Badge::query', 'late', 'present', 'duplicate_of_id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class CommunicationBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_communication_controllers_do_not_loop_recipients_or_call_mail(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Http/Controllers/Api/V2/Communication', ['foreach', 'Mail::', 'Twilio', 'WhatsApp']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture\Concerns;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
trait ScansArchitecture
|
||||
{
|
||||
/** @return string[] */
|
||||
protected function phpFiles(string $relative): array
|
||||
{
|
||||
$root = dirname(__DIR__, 3).'/'.$relative;
|
||||
if (! is_dir($root)) {
|
||||
return [];
|
||||
}
|
||||
$files = [];
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root)) as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||
$files[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
sort($files);
|
||||
return $files;
|
||||
}
|
||||
|
||||
protected function assertFilesDoNotContain(string $relative, array $needles, array $excludePathContains = []): void
|
||||
{
|
||||
foreach ($this->phpFiles($relative) as $file) {
|
||||
foreach ($excludePathContains as $exclude) {
|
||||
if (str_contains($file, $exclude)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$contents = file_get_contents($file) ?: '';
|
||||
foreach ($needles as $needle) {
|
||||
$this->assertStringNotContainsString($needle, $contents, $file.' contains forbidden pattern: '.$needle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class ControllerBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_controllers_do_not_call_external_providers_directly(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Http/Controllers', ['Mail::', 'Notification::send', 'Twilio', 'WhatsApp']);
|
||||
}
|
||||
|
||||
public function test_canonical_controllers_do_not_perform_obvious_domain_mutations(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Http/Controllers/Api/V2', ['DB::table', 'Validator::make', 'Student::max', 'response()->download']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DeprecationPolicyTest extends TestCase
|
||||
{
|
||||
public function test_deprecation_document_exists_with_required_metadata_headings(): void
|
||||
{
|
||||
$path = dirname(__DIR__, 2).'/docs/deprecations.md';
|
||||
$this->assertFileExists($path);
|
||||
$contents = file_get_contents($path) ?: '';
|
||||
foreach (['replacement_route', 'sunset_date', 'owner', 'removal_ticket'] as $required) {
|
||||
$this->assertStringContainsString($required, $contents);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class FinanceBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_finance_module_does_not_use_float_money_math(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Domain/SchoolCore/Finance', ['(float)', 'floatval(']);
|
||||
}
|
||||
|
||||
public function test_finance_controllers_do_not_download_payment_files_directly(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Http/Controllers/Api/V2/Finance', ['response()->download']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class IslamicSundaySchoolBoundaryTest extends TestCase
|
||||
{
|
||||
public function test_extension_route_file_uses_domain_profile_middleware(): void
|
||||
{
|
||||
$routeFile = dirname(__DIR__, 2).'/routes/api_phase8.php';
|
||||
$this->assertFileExists($routeFile);
|
||||
$contents = file_get_contents($routeFile) ?: '';
|
||||
$this->assertStringContainsString('islamic-sunday-school', $contents);
|
||||
$this->assertStringContainsString('ensure.domain.profile:islamic_sunday_school', $contents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class ModuleBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_school_core_does_not_depend_on_islamic_sunday_school(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Domain/SchoolCore', ['App\\Domain\\IslamicSundaySchool']);
|
||||
}
|
||||
|
||||
public function test_core_contracts_use_neutral_vocabulary(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Domain/SchoolCore', ['Quran', "Qur'an", 'Halaqa', 'Masjid', 'Imam', 'Sadaqah', 'Zakat', 'ArabicLevel'], ['README.md']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class Phase10ReleaseReadinessArchitectureTest extends TestCase
|
||||
{
|
||||
public function test_release_docs_exist(): void
|
||||
{
|
||||
$required = [
|
||||
'docs/release/feature-flag-rollout-map.md',
|
||||
'docs/release/migration-validation.md',
|
||||
'docs/release/shadow-comparison-plan.md',
|
||||
'docs/release/rollback-playbooks.md',
|
||||
'docs/release/uat-scenarios.md',
|
||||
'docs/release/support-playbooks.md',
|
||||
'docs/release/production-readiness-review.md',
|
||||
];
|
||||
|
||||
foreach ($required as $path) {
|
||||
$this->assertFileExists(base_path($path), $path);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_release_commands_exist(): void
|
||||
{
|
||||
$required = [
|
||||
'app/Console/Commands/ReleaseFeatureFlagsCommand.php',
|
||||
'app/Console/Commands/ReleaseMigrationValidateCommand.php',
|
||||
'app/Console/Commands/ReleaseShadowCompareCommand.php',
|
||||
'app/Console/Commands/ReleaseReadinessGateCommand.php',
|
||||
'app/Console/Commands/LegacyUnsafeRouteAuditCommand.php',
|
||||
];
|
||||
|
||||
foreach ($required as $path) {
|
||||
$this->assertFileExists(base_path($path), $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
final class Phase2BoundaryArchitectureTest extends TestCase
|
||||
{
|
||||
public function test_school_core_does_not_import_extension_namespace(): void
|
||||
{
|
||||
$violations = $this->scan(app_path('Domain/SchoolCore'), ['App\\Domain\\IslamicSundaySchool']);
|
||||
$this->assertSame([], $violations);
|
||||
}
|
||||
|
||||
public function test_domain_services_do_not_accept_raw_request_or_call_auth(): void
|
||||
{
|
||||
$violations = $this->scan(app_path('Domain'), ['Illuminate\\Http\\Request', 'auth()']);
|
||||
$violations = array_values(array_filter($violations, fn (string $path): bool => ! str_contains($path, 'SchoolContextResolver.php')));
|
||||
$this->assertSame([], $violations);
|
||||
}
|
||||
|
||||
public function test_core_contracts_keep_neutral_vocabulary(): void
|
||||
{
|
||||
$violations = $this->scan(app_path('Domain/SchoolCore/Contracts'), ['masjid', 'ministry', 'bible', 'servant', 'service_day']);
|
||||
$this->assertSame([], $violations);
|
||||
}
|
||||
|
||||
/** @param array<int, string> $needles @return array<int, string> */
|
||||
private function scan(string $root, array $needles): array
|
||||
{
|
||||
if (! is_dir($root)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$violations = [];
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root)) as $file) {
|
||||
if (! $file instanceof SplFileInfo || $file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
$contents = file_get_contents($file->getPathname()) ?: '';
|
||||
foreach ($needles as $needle) {
|
||||
if (str_contains($contents, $needle)) {
|
||||
$violations[] = $file->getPathname() . ' contains ' . $needle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $violations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
final class Phase3FinanceArchitectureTest extends TestCase
|
||||
{
|
||||
private function phpFiles(string $path): array
|
||||
{
|
||||
if (! is_dir($path)) { return []; }
|
||||
$files = [];
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)) as $file) { if ($file->isFile() && $file->getExtension() === 'php') { $files[] = $file->getPathname(); } }
|
||||
return $files;
|
||||
}
|
||||
public function test_school_core_finance_does_not_depend_on_islamic_sunday_school(): void
|
||||
{
|
||||
foreach ($this->phpFiles(__DIR__ . '/../../app/Domain/SchoolCore/Finance') as $file) { $this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool', file_get_contents($file), $file); }
|
||||
}
|
||||
public function test_school_core_finance_does_not_use_request_or_auth_helpers(): void
|
||||
{
|
||||
foreach ($this->phpFiles(__DIR__ . '/../../app/Domain/SchoolCore/Finance') as $file) { $contents=file_get_contents($file); $this->assertStringNotContainsString('Illuminate\\Http\\Request', $contents, $file); $this->assertStringNotContainsString('auth()', $contents, $file); $this->assertStringNotContainsString('request()', $contents, $file); }
|
||||
}
|
||||
public function test_school_core_finance_avoids_float_casts(): void
|
||||
{
|
||||
foreach ($this->phpFiles(__DIR__ . '/../../app/Domain/SchoolCore/Finance') as $file) { $this->assertStringNotContainsString('(float)', file_get_contents($file), $file); }
|
||||
}
|
||||
public function test_no_active_filename_only_payment_file_route_in_phase3_routes(): void
|
||||
{
|
||||
$routes = file_get_contents(__DIR__ . '/../../routes/finance_phase3.php');
|
||||
$this->assertStringNotContainsString("Route::get('payments/files/{filename}'", $routes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class Phase4AttendanceArchitectureTest extends TestCase
|
||||
{
|
||||
public function test_school_core_attendance_does_not_depend_on_islamic_sunday_school(): void
|
||||
{
|
||||
foreach ($this->phpFiles('app/Domain/SchoolCore/Attendance') as $file) {
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool', $contents, $file);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_school_core_attendance_does_not_use_request_or_auth_helpers(): void
|
||||
{
|
||||
foreach ($this->phpFiles('app/Domain/SchoolCore/Attendance') as $file) {
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('Illuminate\\Http\\Request', $contents, $file);
|
||||
$this->assertStringNotContainsString('auth()', $contents, $file);
|
||||
$this->assertStringNotContainsString('request()', $contents, $file);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_school_core_attendance_contracts_remain_neutral(): void
|
||||
{
|
||||
$forbidden = ['masjid', 'ministry', 'quran', 'arabic', 'halaqa'];
|
||||
|
||||
foreach ($this->phpFiles('app/Domain/SchoolCore/Attendance/Contracts') as $file) {
|
||||
$contents = strtolower((string) file_get_contents($file));
|
||||
foreach ($forbidden as $word) {
|
||||
$this->assertStringNotContainsString($word, $contents, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function test_scanner_controller_is_thin(): void
|
||||
{
|
||||
$file = dirname(__DIR__, 2).'/app/Http/Controllers/Attendance/ScannerController.php';
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('private function', $contents);
|
||||
$this->assertStringContainsString('ScannerServiceContract', $contents);
|
||||
}
|
||||
|
||||
private function phpFiles(string $relativePath): array
|
||||
{
|
||||
$base = dirname(__DIR__, 2).'/'.$relativePath;
|
||||
if (! is_dir($base)) {
|
||||
return [];
|
||||
}
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($base));
|
||||
$files = [];
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||
$files[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace Tests\Architecture;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
final class Phase5StudentLifecycleArchitectureTest extends TestCase { private function phpFiles(string $path): array { $base=dirname(__DIR__,2).'/'.$path; if(!is_dir($base)){return [];} $it=new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($base)); $files=[]; foreach($it as $f){ if($f->isFile()&&$f->getExtension()==='php'){$files[]=$f->getPathname();} } return $files; } public function test_school_core_students_does_not_depend_on_islamic_sunday_school(): void { foreach($this->phpFiles('app/Domain/SchoolCore/Students') as $file){$this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool',file_get_contents($file),$file);} } public function test_school_core_students_does_not_depend_on_request_or_auth(): void { foreach($this->phpFiles('app/Domain/SchoolCore/Students') as $file){$c=file_get_contents($file); $this->assertStringNotContainsString('Illuminate\\Http\\Request',$c,$file); $this->assertStringNotContainsString('auth()',$c,$file); $this->assertStringNotContainsString('request()',$c,$file);} } public function test_core_student_contracts_stay_neutral(): void { foreach($this->phpFiles('app/Domain/SchoolCore/Students/Contracts') as $file){$c=strtolower(file_get_contents($file)); foreach(['masjid','quran','arabic','halaqa','ministry','community','islamic_studies'] as $word){$this->assertStringNotContainsString($word,$c,$file);}} } }
|
||||
@@ -0,0 +1 @@
|
||||
<?php declare(strict_types=1); namespace Tests\Architecture; use PHPUnit\Framework\TestCase; final class Phase6CommunicationArchitectureTest extends TestCase { public function test_school_core_communication_has_no_extension_import(): void { $it=new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__.'/../../app/Domain/SchoolCore/Communication')); foreach($it as $file){ if(!$file->isFile()||$file->getExtension()!=='php') continue; $c=file_get_contents($file->getPathname()); $this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool',$c,$file->getPathname()); $this->assertStringNotContainsString('Illuminate\\Http\\Request',$c,$file->getPathname()); $this->assertStringNotContainsString('auth()',$c,$file->getPathname()); $this->assertStringNotContainsString('request()',$c,$file->getPathname()); } } public function test_core_contracts_are_neutral(): void { foreach(glob(__DIR__.'/../../app/Domain/SchoolCore/Communication/Contracts/*.php')?:[] as $file){ $c=strtolower(file_get_contents($file)); foreach(['sunday','masjid','ministry','halaqa','quran'] as $word){ $this->assertStringNotContainsString($word,$c,$file); } } } }
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class Phase7ReportingArchitectureTest extends TestCase
|
||||
{
|
||||
private function files(string $path): array
|
||||
{
|
||||
$base = dirname(__DIR__, 2).'/app/'.$path;
|
||||
return glob($base.'/**/*.php') ?: [];
|
||||
}
|
||||
|
||||
public function test_school_core_reporting_does_not_depend_on_islamic_sunday_school(): void
|
||||
{
|
||||
foreach ($this->files('Domain/SchoolCore/Reporting') as $file) {
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool', $contents, $file);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_reporting_domain_does_not_use_request_or_auth_helpers(): void
|
||||
{
|
||||
foreach ($this->files('Domain/SchoolCore/Reporting') as $file) {
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('Illuminate\\Http\\Request', $contents, $file);
|
||||
$this->assertStringNotContainsString('auth()', $contents, $file);
|
||||
$this->assertStringNotContainsString('request()', $contents, $file);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_core_reporting_contracts_do_not_contain_extension_vocabulary(): void
|
||||
{
|
||||
$forbidden = ['quran', 'halaqa', 'masjid', 'ministry'];
|
||||
foreach ($this->files('Domain/SchoolCore/Reporting/Contracts') as $file) {
|
||||
$contents = strtolower(file_get_contents($file));
|
||||
foreach ($forbidden as $term) {
|
||||
$this->assertStringNotContainsString($term, $contents, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
final class Phase8ControllerApiArchitectureTest extends TestCase
|
||||
{
|
||||
/** @return string[] */
|
||||
private function phpFiles(string $relative): array
|
||||
{
|
||||
$root = dirname(__DIR__, 2).'/'.$relative;
|
||||
if (! is_dir($root)) {
|
||||
return [];
|
||||
}
|
||||
$files = [];
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root)) as $file) {
|
||||
if ($file->isFile() && $file->getExtension() === 'php') {
|
||||
$files[] = $file->getPathname();
|
||||
}
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function test_canonical_v2_controllers_do_not_use_raw_db_or_provider_sdks(): void
|
||||
{
|
||||
foreach ($this->phpFiles('app/Http/Controllers/Api/V2') as $file) {
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('DB::table', $contents, $file);
|
||||
$this->assertStringNotContainsString('Mail::', $contents, $file);
|
||||
$this->assertStringNotContainsString('Validator::make', $contents, $file);
|
||||
$this->assertStringNotContainsString('max(\'id\')', $contents, $file);
|
||||
$this->assertStringNotContainsString('response()->download', $contents, $file);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_schoolcore_controllers_do_not_import_islamic_sunday_school_extension_classes(): void
|
||||
{
|
||||
foreach ($this->phpFiles('app/Http/Controllers/Api/V2') as $file) {
|
||||
if (str_contains($file, 'IslamicSundaySchool')) {
|
||||
continue;
|
||||
}
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('App\\Domain\\IslamicSundaySchool', $contents, $file);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_bulk_send_controller_requires_preview_dto_path(): void
|
||||
{
|
||||
$file = dirname(__DIR__, 2).'/app/Http/Controllers/Api/V2/Communication/BulkSendController.php';
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringContainsString('BulkSendMessageRequest', $contents);
|
||||
$this->assertStringContainsString('sendBulkMessage', $contents);
|
||||
$this->assertStringNotContainsString('foreach', $contents, 'Bulk controller must not loop recipients.');
|
||||
}
|
||||
|
||||
public function test_reporting_controllers_delegate_to_reporting_contracts(): void
|
||||
{
|
||||
$path = dirname(__DIR__, 2).'/app/Http/Controllers/Api/V2/Reporting';
|
||||
foreach ($this->phpFiles('app/Http/Controllers/Api/V2/Reporting') as $file) {
|
||||
$contents = file_get_contents($file);
|
||||
$this->assertStringNotContainsString('select(', $contents, $file);
|
||||
$this->assertStringNotContainsString('join(', $contents, $file);
|
||||
$this->assertStringContainsString('Contract', $contents, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class ReportingBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_reporting_controllers_do_not_build_sql_or_export_directly(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Http/Controllers/Api/V2/Reporting', ['DB::', 'select(', 'join(', 'fputcsv', 'Xlsx']);
|
||||
}
|
||||
|
||||
public function test_schoolcore_reporting_provider_does_not_register_extension_reports(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Providers', ['IslamicSundaySchool\\Reporting\\Reports'], ['IslamicSundaySchoolReportingServiceProvider.php']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class RouteInventoryTest extends TestCase
|
||||
{
|
||||
public function test_generated_route_inventory_exists_and_has_governance_fields(): void
|
||||
{
|
||||
$path = dirname(__DIR__, 2).'/storage/app/generated/api-route-inventory.json';
|
||||
$this->assertFileExists($path);
|
||||
$rows = json_decode(file_get_contents($path) ?: '[]', true);
|
||||
$this->assertIsArray($rows);
|
||||
foreach ($rows as $row) {
|
||||
$this->assertArrayHasKey('module', $row);
|
||||
$this->assertArrayHasKey('canonical_status', $row);
|
||||
$this->assertArrayHasKey('risk', $row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use SplFileInfo;
|
||||
|
||||
final class SchoolCoreArchitectureTest extends TestCase
|
||||
{
|
||||
public function test_school_core_does_not_use_hidden_context_sources(): void
|
||||
{
|
||||
$forbidden = ['auth(', 'request(', 'Configuration::getConfig(', 'App\\Domain\\IslamicSundaySchool'];
|
||||
$root = app_path('Domain/SchoolCore');
|
||||
|
||||
if (! is_dir($root)) {
|
||||
$this->markTestSkipped('SchoolCore directory not present in this test installation.');
|
||||
}
|
||||
|
||||
$violations = [];
|
||||
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root)) as $file) {
|
||||
if (! $file instanceof SplFileInfo || $file->getExtension() !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($file->getPathname()) ?: '';
|
||||
foreach ($forbidden as $needle) {
|
||||
if (str_contains($contents, $needle)) {
|
||||
$violations[] = $file->getPathname() . ' contains ' . $needle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $violations);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class ServiceBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_domain_services_do_not_read_http_or_global_request_state(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Domain', ['Illuminate\\Http\\Request', 'auth()', 'request()', 'session()', '$_GET', '$_POST', '$_SERVER']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace Tests\Architecture;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Tests\Architecture\Concerns\ScansArchitecture;
|
||||
|
||||
final class StudentBoundaryTest extends TestCase
|
||||
{
|
||||
use ScansArchitecture;
|
||||
|
||||
public function test_student_identifier_generation_does_not_use_max_id(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app', ["max('id')", 'max("id")', 'MAX(id)']);
|
||||
}
|
||||
|
||||
public function test_core_student_layer_has_no_extension_profile_fields(): void
|
||||
{
|
||||
$this->assertFilesDoNotContain('app/Domain/SchoolCore/Students', ['quran_level', 'arabic_level', 'halaqa', 'masjid_family', 'imam_note']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user