workflowStorage = $this->diContainer->get(WorkflowStorage::class); $this->workflowRunStorage = $this->diContainer->get(WorkflowRunStorage::class); $this->workflowRunLogStorage = $this->diContainer->get(WorkflowRunLogStorage::class); $this->stepHandler = $this->diContainer->get(StepHandler::class); $this->registry = $this->diContainer->get(Registry::class); $this->wp = new WPFunctions(); } public function testItAllowsSettingData(): void { $log = new WorkflowRunLog(1, 'step-id', []); $this->assertSame([], $log->getData()); $log->setData('key', 'value'); $data = $log->getData(); $this->assertCount(1, $data); $this->assertSame('value', $data['key']); } public function testItGetsExposedViaAction(): void { $this->wp->addAction(Hooks::WORKFLOW_RUN_LOG_AFTER_STEP_RUN, function(Step $step, WorkflowRunLog $log) { $log->setData('test', 'value'); }, 10, 2); $workflowRunLogs = $this->getLogsForAction(); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getData()['test'])->equals('value'); } public function testBadActionIntegrationsCannotDerailStepFromRunning() { $this->wp->addAction(Hooks::WORKFLOW_RUN_LOG_AFTER_STEP_RUN, function(Step $step, WorkflowRunLog $log) { throw new \Exception('bad integration'); }, 9, 2); $workflowRunLogs = $this->getLogsForAction(); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getStatus())->equals(WorkflowRunLog::STATUS_COMPLETED); } public function testItStoresWorkflowRunAndStepIdsCorrectly() { $testAction = $this->getRegisteredTestAction(); $actionStep = new Step('action-step-id', Step::TYPE_ACTION, $testAction->getKey()); $workflow = new Workflow('test_workflow', [$actionStep], new \WP_User()); $workflowId = $this->workflowStorage->createWorkflow($workflow); // Reload to get additional data post-save $workflow = $this->workflowStorage->getWorkflow($workflowId); $this->assertInstanceOf(Workflow::class, $workflow); $workflowRun = new WorkflowRun($workflowId, $workflow->getVersionId(), 'trigger-key', []); $workflowRunId = $this->workflowRunStorage->createWorkflowRun($workflowRun); $this->stepHandler->handle([ 'workflow_run_id' => $workflowRunId, 'step_id' => 'action-step-id' ]); $log = $this->workflowRunLogStorage->getLogsForWorkflowRun($workflowRunId)[0]; expect($log->getWorkflowRunId())->equals($workflowRunId); expect($log->getStepId())->equals('action-step-id'); } public function testItLogsCompletedStatusCorrectly(): void { $workflowRunLogs = $this->getLogsForAction(function() { return true; }); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getStatus())->equals('completed'); } public function testItAddsCompletedAtTimestampAfterRunningSuccessfully(): void { $this->wp->addAction(Hooks::WORKFLOW_RUN_LOG_AFTER_STEP_RUN, function(Step $step, WorkflowRunLog $log) { expect($log->getCompletedAt())->null(); }, 10, 2); $workflowRunLogs = $this->getLogsForAction(function() { return true; }); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getCompletedAt())->isInstanceOf(\DateTimeImmutable::class); } public function testItLogsFailedStatusCorrectly(): void { $workflowRunLogs = $this->getLogsForAction(function() { throw new \Exception('error'); }); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getStatus())->equals('failed'); } public function testItDoesNotHaveCompletedAtIfItFailsToRun(): void { $workflowRunLogs = $this->getLogsForAction(function() { throw new \Exception('error'); }); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getCompletedAt())->null(); } public function testItIncludesErrorOnFailure(): void { $workflowRunLogs = $this->getLogsForAction(function() { throw new \Exception('error', 12345); }); expect($workflowRunLogs)->count(1); $log = $workflowRunLogs[0]; expect($log->getErrors())->count(1); $error = $log->getErrors()[0]; expect($error['message'])->equals('error'); expect($error['code'])->equals(12345); expect($error['exceptionClass'])->equals('Exception'); expect($error['trace'])->array(); expect(count($error['trace']))->greaterThan(0); } public function testItLogsStepArgs(): void { $log = $this->getLogsForAction()[0]; expect($log->getArgs())->count(2); expect(array_keys($log->getArgs()))->equals(['workflow_run_id', 'step_id']); } public function _after() { global $wpdb; $sql = 'truncate ' . $wpdb->prefix . 'mailpoet_workflow_run_logs'; $wpdb->query($sql); $sql = 'truncate ' . $wpdb->prefix . 'mailpoet_workflows'; $wpdb->query($sql); $sql = 'truncate ' . $wpdb->prefix . 'mailpoet_workflow_versions'; $wpdb->query($sql); $sql = 'truncate ' . $wpdb->prefix . 'mailpoet_workflow_runs'; $wpdb->query($sql); } private function getLogsForAction($callback = null) { $testAction = $this->getRegisteredTestAction($callback); $actionStep = new Step('action-step-id', Step::TYPE_ACTION, $testAction->getKey()); $workflow = new Workflow('test_workflow', [$actionStep], new \WP_User()); $workflowId = $this->workflowStorage->createWorkflow($workflow); // Reload to get additional data post-save $workflow = $this->workflowStorage->getWorkflow($workflowId); $this->assertInstanceOf(Workflow::class, $workflow); $workflowRun = new WorkflowRun($workflowId, $workflow->getVersionId(), 'trigger-key', []); $workflowRunId = $this->workflowRunStorage->createWorkflowRun($workflowRun); try { $this->stepHandler->handle([ 'workflow_run_id' => $workflowRunId, 'step_id' => 'action-step-id' ]); } catch (\Exception $e) { // allow exceptions so we can test failure states } return $this->workflowRunLogStorage->getLogsForWorkflowRun($workflowRunId); } private function getRegisteredTestAction($callback = null) { if ($callback === null) { $callback = function() { return true; }; } $action = new TestAction(); $action->setCallback($callback); $this->registry->addAction($action); return $action; } } class TestAction implements Action { private $callback; private $key; public function __construct() { $this->key = Security::generateRandomString(10); } public function setCallback($callback) { $this->callback = $callback; } public function isValid(array $subjects, Step $step, Workflow $workflow): bool { return true; } public function run(Workflow $workflow, WorkflowRun $workflowRun, Step $step): void { if ($this->callback) { ($this->callback)($workflow, $workflowRun, $step); } } public function getKey(): string { return $this->key; } public function getName(): string { return 'Test Action'; } public function getArgsSchema(): ObjectSchema { return Builder::object(); } }