From ef623260db3edf657d31df34d14150dc80175448 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 30 Jun 2026 06:22:30 +0000 Subject: [PATCH] Add document approval workflow pause/resume integration test Closes #17 --- .../DocumentApprovalWorkflowTest.php | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 tests/Integration/StateFlow/DocumentApprovalWorkflowTest.php diff --git a/tests/Integration/StateFlow/DocumentApprovalWorkflowTest.php b/tests/Integration/StateFlow/DocumentApprovalWorkflowTest.php new file mode 100644 index 0000000..1ff7165 --- /dev/null +++ b/tests/Integration/StateFlow/DocumentApprovalWorkflowTest.php @@ -0,0 +1,204 @@ + 'APR-2026-001', + 'requested_by' => 'user-42', + 'status' => 'pending_approval', + ]; + + $permissionGate = $this->createPermissionGate($log); + $validateFormatAction = $this->createValidateFormatAction($log); + $createApprovalRequestAction = $this->createApprovalRequestAction($log, $approvalMetadata); + $publishDocumentAction = $this->createPublishDocumentAction($log); + $sendNotificationsAction = $this->createSendNotificationsAction($log); + + $configuration = Configuration::fromArray( + [$permissionGate], + [$validateFormatAction, $createApprovalRequestAction, $publishDocumentAction, $sendNotificationsAction] + ); + + $stateFlow = new StateFlow(fn () => $configuration); + + $draftState = $this->createTestState(['status' => 'draft', 'title' => 'Q3 Financial Report']); + + // Step 1: transition draft -> published, expect pause after the approval request action + $worker = $stateFlow->transition($draftState, new ArrayDelta(['status' => 'published'])); + $pausedContext = $worker->execute(); + + $this->assertSame(['PermissionGate', 'ValidateFormat', 'CreateApprovalRequest'], $log); + $this->assertTrue($pausedContext->executionStatus()->isPaused()); + $this->assertFalse($pausedContext->executionStatus()->isCompleted()); + $this->assertCount(2, $pausedContext->executionHistory()->getActionExecutions()); + $this->assertSame($approvalMetadata, $pausedContext->getStatusMetadata()); + $this->assertSame('draft', $pausedContext->getCurrentState()->toArray()['status']); + + // Step 2: resume with approval granted - publish and send notifications should run + $resumedWorker = $stateFlow->fromContext($pausedContext); + $completedContext = $resumedWorker->execute(); + + $this->assertSame( + ['PermissionGate', 'ValidateFormat', 'CreateApprovalRequest', 'PermissionGate', 'PublishDocument', 'SendNotifications'], + $log + ); + $this->assertTrue($completedContext->executionStatus()->isCompleted()); + $this->assertFalse($completedContext->executionStatus()->isPaused()); + $this->assertCount(4, $completedContext->executionHistory()->getActionExecutions()); + $this->assertSame('published', $completedContext->getCurrentState()->toArray()['status']); + $this->assertSame($pausedContext, $completedContext); + } + + /** + * @param array $log + */ + private function createPermissionGate(array &$log): Gate + { + return new class ($log) implements Gate + { + /** + * @param array $log + */ + public function __construct( + /** @phpstan-ignore property.onlyWritten */ + private array &$log + ) {} + + public function evaluate(GateContext $context): GateResult + { + $this->log[] = 'PermissionGate'; + + return GateResult::ALLOW; + } + + public function message(): string + { + return 'User has permission to publish'; + } + }; + } + + /** + * @param array $log + */ + private function createValidateFormatAction(array &$log): Action + { + return new class ($log) implements Action + { + /** + * @param array $log + */ + public function __construct( + /** @phpstan-ignore property.onlyWritten */ + private array &$log + ) {} + + public function execute(ActionContext $context): ActionResult + { + $this->log[] = 'ValidateFormat'; + + return ActionResult::continue(); + } + }; + } + + /** + * @param array $log + * @param array $metadata + */ + private function createApprovalRequestAction(array &$log, array $metadata): Action + { + return new class ($log, $metadata) implements Action + { + /** + * @param array $log + * @param array $metadata + */ + public function __construct( + /** @phpstan-ignore property.onlyWritten */ + private array &$log, + private array $metadata + ) {} + + public function execute(ActionContext $context): ActionResult + { + $this->log[] = 'CreateApprovalRequest'; + + return ActionResult::pause(null, $this->metadata); + } + }; + } + + /** + * @param array $log + */ + private function createPublishDocumentAction(array &$log): Action + { + return new class ($log) implements Action + { + /** + * @param array $log + */ + public function __construct( + /** @phpstan-ignore property.onlyWritten */ + private array &$log + ) {} + + public function execute(ActionContext $context): ActionResult + { + $this->log[] = 'PublishDocument'; + + return ActionResult::continue($context->currentState->with(['status' => 'published'])); + } + }; + } + + /** + * @param array $log + */ + private function createSendNotificationsAction(array &$log): Action + { + return new class ($log) implements Action + { + /** + * @param array $log + */ + public function __construct( + /** @phpstan-ignore property.onlyWritten */ + private array &$log + ) {} + + public function execute(ActionContext $context): ActionResult + { + $this->log[] = 'SendNotifications'; + + return ActionResult::continue(); + } + }; + } +}