make(Kernel::class)->bootstrap(); $config = DB::connection('pgsql')->getConfig(); unset($config['name']); if ($config['host'] !== '127.0.0.1') { throw new LogicException('Prova de contexto apenas em servidor local.'); } $database = 'compras_receipts_test_'.bin2hex(random_bytes(6)); config(['database.connections.context_proof_admin' => [...$config, 'database' => 'postgres', 'url' => null]]); $admin = DB::connection('context_proof_admin'); $admin->statement('CREATE DATABASE "'.$database.'"'); $workflow = in_array('--workflow', $argv ?? [], true); $pilot = in_array('--pilot', $argv ?? [], true); $workdesk = in_array('--workdesk', $argv ?? [], true); $readiness = in_array('--readiness', $argv ?? [], true); $exchange = in_array('--exchange', $argv ?? [], true); $transport = in_array('--transport', $argv ?? [], true); $internalTrace = in_array('--internal-trace', $argv ?? [], true); $taskInbox = in_array('--task-inbox', $argv ?? [], true); $financeIntegration = in_array('--finance-integration', $argv ?? [], true); $awardTasks = in_array('--award-tasks', $argv ?? [], true); $quotationTasks = in_array('--quotation-tasks', $argv ?? [], true); $organizationPreview = in_array('--organization-preview', $argv ?? [], true); $pilotAccessProposal = in_array('--pilot-access-proposal', $argv ?? [], true); $formalTasks = in_array('--formal-tasks', $argv ?? [], true); $supplyTasks = in_array('--supply-tasks', $argv ?? [], true); $historyTasks = in_array('--history-tasks', $argv ?? [], true); $suggestionTasks = in_array('--suggestion-tasks', $argv ?? [], true); $alternativeTasks = in_array('--alternative-tasks', $argv ?? [], true); $supplierTasks = in_array('--supplier-tasks', $argv ?? [], true); $dispositionTasks = in_array('--disposition-tasks', $argv ?? [], true); $inventoryTasks = in_array('--inventory-tasks', $argv ?? [], true); $lotTasks = in_array('--lot-tasks', $argv ?? [], true); $improvementTasks = in_array('--improvement-tasks', $argv ?? [], true); $qualityTasks = in_array('--quality-tasks', $argv ?? [], true); $coordinationTasks = in_array('--coordination-tasks', $argv ?? [], true); $lotQualityTasks = in_array('--lot-quality-tasks', $argv ?? [], true); $lotPagination = in_array('--lot-pagination', $argv ?? [], true); $supplyPagination = in_array('--supply-pagination', $argv ?? [], true); $correctionPagination = in_array('--correction-pagination', $argv ?? [], true); $reservationSources = in_array('--reservation-sources', $argv ?? [], true); $historySources = in_array('--history-sources', $argv ?? [], true); $policySources = in_array('--policy-sources', $argv ?? [], true); $correctionSources = in_array('--correction-sources', $argv ?? [], true); $supplyLineSources = in_array('--supply-line-sources', $argv ?? [], true); $coordinationFilters = in_array('--coordination-filters', $argv ?? [], true); $coordinationExport = in_array('--coordination-export', $argv ?? [], true); $taskDeadlines = in_array('--task-deadlines', $argv ?? [], true); $noticeConsultation = in_array('--notice-consultation', $argv ?? [], true); $agreementSources = in_array('--agreement-sources', $argv ?? [], true); $mfaFoundation = in_array('--mfa-foundation', $argv ?? [], true); $mfaManagement = in_array('--mfa-management', $argv ?? [], true); $requisitionAudit = in_array('--requisition-audit', $argv ?? [], true); $auditPagination = in_array('--audit-pagination', $argv ?? [], true); $neededAttention = in_array('--needed-attention', $argv ?? [], true); $approvedAttention = in_array('--approved-attention', $argv ?? [], true); $replenishmentHealth = in_array('--replenishment-health', $argv ?? [], true); $receiptBlock = in_array('--receipt-block', $argv ?? [], true); $inbox = in_array('--inbox', $argv ?? [], true) || $transport; $warehouse = in_array('--warehouse', $argv ?? [], true) || $pilot || $workdesk || $readiness; try { $environment = ['APP_ENV' => 'testing', 'DB_CONNECTION' => 'pgsql', 'DB_URL' => '', 'DB_HOST' => $config['host'], 'DB_PORT' => (string) $config['port'], 'DB_DATABASE' => $database, 'DB_USERNAME' => $config['username'], 'DB_PASSWORD' => $config['password']]; $tests = $workflow ? ['tests/Feature/InternalRequisitionCandidateTest.php', 'tests/Feature/WorkContextTest.php'] : ['tests/Feature/WorkContextTest.php']; if ($warehouse) { $tests = ['tests/Feature/WarehouseRequestsTest.php', 'tests/Feature/WarehouseAvailabilityTest.php', 'tests/Feature/WarehouseFulfilmentTest.php', 'tests/Feature/AutomaticPurchasesTest.php']; } if ($readiness) { $tests = [...$tests, 'tests/Feature/WarehousePilotReadinessTest.php', 'tests/Feature/WarehousePilotJourneyTest.php']; } if ($exchange) { $tests = ['tests/Feature/WarehouseExchangeTest.php']; } if ($inbox) { $environment['INBOX_TEST_PG'] = '1'; $tests = ['tests/Unit/WarehouseInboxTest.php', 'tests/Feature/WarehouseExchangeTest.php']; } if ($transport) { $tests[] = 'tests/Unit/WarehouseHttpSourceTest.php'; } if ($internalTrace) { $tests = ['tests/Feature/InternalSupplyTraceTest.php', 'tests/Feature/WarehousePilotJourneyTest.php']; } if ($taskInbox) { $tests = ['tests/Feature/WorkflowTaskInboxTest.php', 'tests/Feature/InternalSupplyTraceTest.php', 'tests/Feature/WarehousePilotJourneyTest.php']; } if ($financeIntegration) { $tests = ['tests/Feature/FinanceIntegrationTest.php', 'tests/Feature/PaymentIntegrationTest.php', 'tests/Feature/InvoiceTest.php', 'tests/Feature/ExternalPaymentTest.php', 'tests/Feature/PurchaseProcessTest.php', 'tests/Feature/PayablePaymentDecisionTest.php']; } if ($awardTasks) { $tests = ['tests/Feature/PurchaseAwardOrderTest.php']; } if ($quotationTasks) { $tests = ['tests/Feature/SupplierQuotationTest.php', 'tests/Feature/PurchaseAwardOrderTest.php']; } if ($organizationPreview) { $tests = ['tests/Feature/OrganizationActivationPreviewTest.php', 'tests/Feature/WorkContextTest.php']; } if ($pilotAccessProposal) { $tests = ['tests/Feature/PilotAccessProposalTest.php']; } if ($formalTasks) { $tests = ['tests/Feature/FormalAdmissionTest.php', 'tests/Feature/FormalRecommendationTest.php', 'tests/Feature/DocumentaryQualificationTest.php']; } if ($supplyTasks) { $tests = ['tests/Feature/SupplyTaskIntegrationTest.php', 'tests/Feature/AutomaticPurchasesTest.php']; } if ($historyTasks) { $tests = ['tests/Feature/SupplyHistoryTest.php', 'tests/Feature/WarehousePilotReadinessTest.php']; } if ($suggestionTasks) { $tests = ['tests/Feature/SupplySuggestionsTest.php']; } if ($alternativeTasks) { $tests = ['tests/Feature/SupplyCoordinationTest.php']; } if ($supplierTasks) { $tests = ['tests/Feature/SupplierMaintenanceTest.php']; } if ($dispositionTasks) { $tests = ['tests/Feature/InventoryDispositionsTest.php']; } if ($inventoryTasks) { $tests = ['tests/Feature/InventoryDispositionsTest.php', 'tests/Feature/InventoryReversalsTest.php']; } if ($lotTasks) { $tests = ['tests/Feature/InventoryLotsTest.php']; } if ($improvementTasks) { $tests = ['tests/Feature/SupplierImprovementTest.php']; } if ($qualityTasks) { $tests = ['tests/Feature/SupplierEvaluationTest.php', 'tests/Feature/SupplierResponseTest.php']; } if ($coordinationTasks) { $tests = ['tests/Feature/SupplyCoordinationTest.php', 'tests/Feature/InventoryReversalsTest.php']; } if ($lotQualityTasks) { $tests = ['tests/Feature/InventoryLotsTest.php']; } if ($lotPagination) { $tests = ['tests/Feature/InventoryLotsTest.php']; } if ($supplyPagination) { $tests = ['tests/Feature/SupplyPaginationTest.php', 'tests/Feature/SupplyHistoryTest.php']; } if ($correctionPagination) { $tests = ['tests/Feature/InventoryCorrectionPaginationTest.php']; } if ($reservationSources) { $tests = ['tests/Feature/SupplyCoordinationTest.php', 'tests/Feature/InventoryReversalsTest.php']; } if ($historySources) { $tests = ['tests/Feature/SupplyHistoryTest.php']; } if ($policySources) { $tests = ['tests/Feature/SupplySuggestionsTest.php']; } if ($correctionSources) { $tests = ['tests/Feature/InventoryCorrectionPaginationTest.php']; } if ($supplyLineSources) { $tests = ['tests/Feature/SupplyCoordinationTest.php']; } if ($coordinationFilters || $coordinationExport) { $tests = ['tests/Feature/SupplyCoordinationTest.php']; } if ($taskDeadlines) { $tests = ['tests/Feature/WorkflowTaskInboxTest.php', 'tests/Feature/SupplyCoordinationTest.php']; } if ($noticeConsultation) { $tests = ['tests/Feature/InternalRequisitionCandidateTest.php']; } if ($agreementSources) { $tests = ['tests/Feature/SupplyPaginationTest.php']; } if ($mfaFoundation) { $tests = ['tests/Feature/Auth/TwoFactorFoundationTest.php']; } if ($mfaManagement) { $tests = ['tests/Feature/Settings/TwoFactorManagementTest.php']; } if ($requisitionAudit || $auditPagination) { $tests = ['tests/Feature/InternalRequisitionCandidateTest.php']; } if ($neededAttention || $approvedAttention) { $tests = ['tests/Feature/ProcurementReportsTest.php']; } if ($replenishmentHealth) { $tests = ['tests/Feature/ReplenishmentHealthTest.php']; } if ($receiptBlock) { $tests = ['tests/Feature/ReceiptDocumentBlockTest.php']; } $filter = match (true) { $receiptBlock => ['--filter=receipt document block'], $replenishmentHealth => ['--filter=replenishment health'], $approvedAttention => ['--filter=approved attention reports'], $neededAttention => ['--filter=needed attention report'], $auditPagination => ['--filter=requisition audit export'], $requisitionAudit => ['--filter=requisition audit export'], $mfaManagement => ['--filter=mfa management'], $mfaFoundation => ['--filter=two factor foundation'], $agreementSources => ['--filter=agreement source search'], $noticeConsultation => ['--filter=notice consultation'], $taskDeadlines => ['--filter=task deadline'], $coordinationExport => ['--filter=coordination export'], $coordinationFilters => ['--filter=coordination record filters'], $supplyLineSources => ['--filter=supply line search'], $correctionSources => ['--filter=older selected|correction source search'], $policySources => ['--filter=policy source search'], $historySources => ['--filter=history source search'], $reservationSources => ['--filter=reservation source search|coordination task returns'], $supplyPagination => ['--filter=supply pagination'], $lotPagination => ['--filter=lot pagination'], $lotQualityTasks => ['--filter=lot quality task'], $coordinationTasks => ['--filter=coordination task'], $qualityTasks => ['--filter=quality task|response task'], $improvementTasks => ['--filter=improvement task'], $lotTasks => ['--filter=lot setup task'], $inventoryTasks => ['--filter=disposition task|reversal task'], $dispositionTasks => ['--filter=disposition task'], $supplierTasks => ['--filter=supplier task'], $alternativeTasks => ['--filter=alternative task'], $suggestionTasks => ['--filter=suggestion task'], $historyTasks => ['--filter=history task|command readiness'], $supplyTasks => ['--filter=supply task'], $formalTasks => ['--filter=formal task'], $quotationTasks => ['--filter=quotation tasks'], $awardTasks => ['--filter=task inbox'], default => [], }; $process = new Process([PHP_BINARY, 'vendor/bin/pest', '--compact', ...$tests, ...$filter], dirname(__DIR__), $environment, timeout: $financeIntegration || $awardTasks || $quotationTasks || $organizationPreview || $pilotAccessProposal || $formalTasks || $supplyTasks || $historyTasks || $suggestionTasks || $alternativeTasks || $supplierTasks || $dispositionTasks || $inventoryTasks || $lotTasks || $improvementTasks || $qualityTasks || $coordinationTasks || $lotQualityTasks || $lotPagination || $supplyPagination || $correctionPagination || $reservationSources || $historySources || $policySources || $correctionSources || $supplyLineSources || $noticeConsultation ? 300 : 120); $process->run(); echo $process->getOutput(); if (! $process->isSuccessful()) { throw new RuntimeException('Prova PostgreSQL de contexto não aprovada.'); } $races = []; if ($inbox) { require __DIR__.'/../tests/Support/inbox-pg-races.php'; $races = inboxPgRaces($environment, $config, $database); } if ($warehouse) { config(['database.connections.warehouse_proof_read' => [...$config, 'database' => $database, 'url' => null]]); $reader = DB::connection('warehouse_proof_read'); foreach (['same_operation', 'different_operations'] as $scenario) { $fixture = new Process([PHP_BINARY, 'tests/Concurrency/warehouse-worker.php', 'prepare'], dirname(__DIR__), $environment, timeout: 20); $fixture->mustRun(); $data = json_decode($fixture->getOutput(), true, 32, JSON_THROW_ON_ERROR); $first = new Process([PHP_BINARY, 'tests/Concurrency/warehouse-worker.php', 'hold', json_encode($data, JSON_THROW_ON_ERROR)], dirname(__DIR__), $environment, timeout: 20); $first->start(); $deadline = microtime(true) + 5; while ($first->isRunning() && ! str_contains($first->getOutput(), 'locked') && microtime(true) < $deadline) { usleep(20000); } if (! str_contains($first->getOutput(), 'locked')) { throw new RuntimeException('Primeiro executor sem bloqueio demonstrado.'); } if ($scenario === 'different_operations') { $data['input']['operation_key'] = (string) Str::uuid(); } $second = new Process([PHP_BINARY, 'tests/Concurrency/warehouse-worker.php', 'run', json_encode($data, JSON_THROW_ON_ERROR)], dirname(__DIR__), $environment, timeout: 20); $second->start(); $blocked = false; $deadline = microtime(true) + 5; while ($first->isRunning() && microtime(true) < $deadline) { $blocked = (bool) $reader->selectOne("select exists(select 1 from pg_stat_activity where datname=? and wait_event_type='Lock') as blocked", [$database])->blocked; if ($blocked) { break; } usleep(20000); } $first->wait(); $second->wait(); $a = json_decode(trim(str_replace('locked', '', $first->getOutput())), true, 16, JSON_THROW_ON_ERROR); $b = json_decode(trim($second->getOutput()), true, 16, JSON_THROW_ON_ERROR); if (! $blocked || $a['result'] !== 'saved' || ($scenario === 'same_operation' ? ($b['result'] !== 'saved' || $a['event'] !== $b['event']) : $b['result'] !== 'rejected')) { throw new RuntimeException('Disputa não preservou entrega única.'); } if ((int) $reader->table('warehouse_request_events')->where('warehouse_request_id', $data['request'])->where('action', 'deliver')->count() !== 1 || (int) $reader->table('stock_movements')->where('tenant_id', $data['tenant'])->where('stock_item_id', $data['stock'])->where('warehouse_id', $data['warehouse'])->sum('quantity_milli') !== 6000) { throw new RuntimeException('Movimento ou entrega duplicados.'); } $races[] = ['scenario' => $scenario, 'real_lock_contention' => true, 'result' => 'passed']; } $fixture = new Process([PHP_BINARY, 'tests/Concurrency/replenishment-worker.php', 'prepare'], dirname(__DIR__), $environment, timeout: 30); $fixture->mustRun(); $data = json_decode($fixture->getOutput(), true, 32, JSON_THROW_ON_ERROR); $first = new Process([PHP_BINARY, 'tests/Concurrency/replenishment-worker.php', 'hold', json_encode($data, JSON_THROW_ON_ERROR)], dirname(__DIR__), $environment, timeout: 30); $first->start(); $deadline = microtime(true) + 5; while ($first->isRunning() && ! str_contains($first->getOutput(), 'locked') && microtime(true) < $deadline) { usleep(20000); } if (! str_contains($first->getOutput(), 'locked')) { throw new RuntimeException('Falta bloqueio da compra automática.'); } $second = new Process([PHP_BINARY, 'tests/Concurrency/replenishment-worker.php', 'run', json_encode($data, JSON_THROW_ON_ERROR)], dirname(__DIR__), $environment, timeout: 30); $second->start(); $blocked = false; $deadline = microtime(true) + 5; while ($first->isRunning() && microtime(true) < $deadline) { $blocked = (bool) $reader->selectOne("select exists(select 1 from pg_stat_activity where datname=? and wait_event_type='Lock') as blocked", [$database])->blocked; if ($blocked) { break; }usleep(20000); } $first->wait(); $second->wait(); if (! $first->isSuccessful() || ! $second->isSuccessful()) { throw new RuntimeException('Falha na activação concorrente: '.$first->getErrorOutput().$second->getErrorOutput()); } $a = json_decode(trim(str_replace('locked', '', $first->getOutput())), true, 32, JSON_THROW_ON_ERROR); $b = json_decode(trim($second->getOutput()), true, 32, JSON_THROW_ON_ERROR); if (! $blocked || $a['rule'] !== $b['rule'] || count($a['purchases']) !== 1 || $a['purchases'] !== $b['purchases'] || (int) $reader->table('requisitions')->where('tenant_id', $data['tenant'])->where('status', 'draft')->count() !== 1) { throw new RuntimeException('A reposição concorrente duplicou o rascunho.'); } $races[] = ['scenario' => 'automatic_purchase_activation_retry', 'real_lock_contention' => true, 'single_draft' => true, 'result' => 'passed']; if ($pilot) { $fixture = new Process([PHP_BINARY, 'tests/Concurrency/recovery-worker.php', 'prepare'], dirname(__DIR__), $environment, timeout: 30); $fixture->mustRun(); $data = json_decode($fixture->getOutput(), true, 32, JSON_THROW_ON_ERROR); $before = $reader->table('replenishment_work')->where('id', $data['work'])->sole(); if ($before->generation <= $before->processed_generation || $reader->table('requisitions')->where('tenant_id', $data['tenant'])->where('status', 'draft')->exists()) { throw new RuntimeException('A prova não deixou sinal durável sem rascunho após interrupção.'); } $first = new Process([PHP_BINARY, 'tests/Concurrency/recovery-worker.php', 'hold', json_encode($data, JSON_THROW_ON_ERROR)], dirname(__DIR__), $environment, timeout: 30); $first->start(); $deadline = microtime(true) + 5; while ($first->isRunning() && ! str_contains($first->getOutput(), 'locked') && microtime(true) < $deadline) { usleep(20000); } if (! str_contains($first->getOutput(), 'locked')) { throw new RuntimeException('Primeiro recuperador sem bloqueio.'); } $second = new Process([PHP_BINARY, 'tests/Concurrency/recovery-worker.php', 'run', json_encode($data, JSON_THROW_ON_ERROR)], dirname(__DIR__), $environment, timeout: 30); $second->start(); $blocked = false; $deadline = microtime(true) + 5; while ($first->isRunning() && microtime(true) < $deadline) { $blocked = (bool) $reader->selectOne("select exists(select 1 from pg_stat_activity where datname=? and wait_event_type='Lock') as blocked", [$database])->blocked; if ($blocked) { break; }usleep(20000); } $first->wait(); $second->wait(); if (! $first->isSuccessful() || ! $second->isSuccessful()) { throw new RuntimeException('Recuperação concorrente falhou.'); } $a = json_decode(trim(str_replace('locked', '', $first->getOutput())), true, 32, JSON_THROW_ON_ERROR); $b = json_decode(trim($second->getOutput()), true, 32, JSON_THROW_ON_ERROR); $after = $reader->table('replenishment_work')->where('id', $data['work'])->sole(); if (! $blocked || $a['result'] !== 'saved' || $b['result'] !== 'saved' || $a['authenticated'] || $b['authenticated'] || count($a['drafts']) !== 1 || $a['drafts'] !== $b['drafts'] || $after->generation !== $after->processed_generation) { throw new RuntimeException('A recuperação perdeu ou duplicou a compra.'); } $races[] = ['scenario' => 'abrupt_exit_after_physical_commit_then_two_recovery_workers', 'real_lock_contention' => true, 'single_draft' => true, 'no_authenticated_human' => true, 'result' => 'passed']; } DB::purge('warehouse_proof_read'); } $proofName = match (true) { $receiptBlock => 'receipt-block-pg', $replenishmentHealth => 'replenishment-health-pg', $approvedAttention => 'approved-attention-pg', $neededAttention => 'needed-attention-pg', $auditPagination => 'audit-pagination-pg', $requisitionAudit => 'requisition-audit-pg', $mfaManagement => 'mfa-management-pg', $mfaFoundation => 'mfa-foundation-pg', $agreementSources => 'agreement-source-pg', $noticeConsultation => 'notice-consultation-pg', $taskDeadlines => 'task-deadlines-pg', $coordinationExport => 'coordination-export-pg', $coordinationFilters => 'coordination-filters-pg', $supplyLineSources => 'supply-line-source-pg', $correctionSources => 'correction-source-pg', $policySources => 'policy-source-pg', $historySources => 'history-source-pg', $reservationSources => 'reservation-source-pg', $correctionPagination => 'inventory-pagination-pg', $supplyPagination => 'supply-pagination-pg', $lotPagination => 'lot-pagination-pg', $lotQualityTasks => 'lot-quality-task-integration-pg', $coordinationTasks => 'coordination-task-integration-pg', $qualityTasks => 'supplier-quality-task-integration-pg', $improvementTasks => 'supplier-improvement-task-integration-pg', $lotTasks => 'lot-task-integration-pg', $inventoryTasks => 'inventory-task-integration-pg', $dispositionTasks => 'disposition-task-integration-pg', $supplierTasks => 'supplier-task-integration-pg', $alternativeTasks => 'alternative-task-integration-pg', $suggestionTasks => 'suggestion-task-integration-pg', $historyTasks => 'history-task-integration-pg', $supplyTasks => 'supply-task-integration-pg', $formalTasks => 'formal-task-integration-pg', $pilotAccessProposal => 'pilot-access-proposal-pg', $organizationPreview => 'organization-activation-preview-pg', $quotationTasks => 'quotation-task-integration-pg', $awardTasks => 'award-task-integration-pg', $financeIntegration => 'finance-integration-pg', $taskInbox => 'workflow-task-inbox-pg', $internalTrace => 'internal-supply-integration-pg', $transport => 'warehouse-http-source-pg', $inbox => 'warehouse-inbox-pg', $exchange => 'warehouse-exchange-pg', $readiness => 'warehouse-pilot-readiness-pg', $workdesk => 'warehouse-workdesk-pg', $pilot => 'replenishment-pilot-pg', $warehouse => 'warehouse-drafts-pg', $workflow => 'workflow-notices-pg', default => 'work-context-pg', }; file_put_contents(dirname(__DIR__).'/docs/proofs/'.$proofName.'.json', json_encode(['checked_at' => gmdate('c'), 'database' => 'disposable PostgreSQL', 'result' => trim($process->getOutput()), 'races' => $races, 'persistent_data_changed' => false, 'database_removed_in_finally' => true], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR)); } finally { $admin->select('select pg_terminate_backend(pid) from pg_stat_activity where datname = ? and pid <> pg_backend_pid()', [$database]); $admin->statement('DROP DATABASE "'.$database.'"'); }