automationStorage = $this->diContainer->get(AutomationStorage::class); $this->automationRunStorage = $this->diContainer->get(AutomationRunStorage::class); $this->automationRunLogStorage = $this->diContainer->get(AutomationRunLogStorage::class); $this->stepHandler = $this->diContainer->get(StepHandler::class); $this->registry = $this->diContainer->get(Registry::class); $this->wp = new WPFunctions(); } public function testItAllowsSettingSimpleData(): void { $log = new AutomationRunLog(1, 'step-id', AutomationRunLog::TYPE_ACTION); $this->assertSame([], $log->getData()); $log->setData('key', 'value'); $data = $log->getData(); $this->assertCount(1, $data); $this->assertSame('value', $data['key']); } public function testItAllowsSettingArraysOfScalarValues(): void { $log = new AutomationRunLog(1, 'step-id', AutomationRunLog::TYPE_ACTION); $data = [ 'string', 11.1, 10, true, false, ]; $log->setData('data', $data); $this->automationRunLogStorage->createAutomationRunLog($log); $retrieved = $this->automationRunLogStorage->getLogsForAutomationRun(1)[0]; expect($retrieved->getData()['data'])->equals($data); } public function testItAllowsSettingMultidimensionalArraysOfScalarValues(): void { $log = new AutomationRunLog(1, 'step-id', AutomationRunLog::TYPE_ACTION); $data = [ 'values' => [ 'string', 11.1, 10, true, false, ], ]; $log->setData('data', $data); $this->automationRunLogStorage->createAutomationRunLog($log); $retrieved = $this->automationRunLogStorage->getLogsForAutomationRun(1)[0]; expect($retrieved->getData()['data'])->equals($data); } public function testItDoesNotAllowSettingDataThatIncludesClosures(): void { $log = new AutomationRunLog(1, 'step-id', AutomationRunLog::TYPE_ACTION); $badData = [ function() { echo 'closures cannot be serialized'; }, ]; $this->expectException(\InvalidArgumentException::class); $log->setData('badData', $badData); expect($log->getData())->count(0); } public function testItDoesNotAllowSettingObjectsForData(): void { $log = new AutomationRunLog(1, 'step-id', AutomationRunLog::TYPE_ACTION); $object = new stdClass(); $object->key = 'value'; $this->expectException(\InvalidArgumentException::class); $log->setData('object', $object); expect($log->getData())->count(0); } public function testItDoesNotAllowSettingMultidimensionalArrayThatContainsNonScalarValue(): void { $log = new AutomationRunLog(1, 'step-id', AutomationRunLog::TYPE_ACTION); $data = [ 'test' => [ 'multidimensional' => [ 'array' => [ 'values' => [ new stdClass(), ], ], ], ], ]; $this->expectException(\InvalidArgumentException::class); $log->setData('data', $data); expect($log->getData())->count(0); } public function testItGetsExposedViaAction(): void { $this->wp->addAction(Hooks::AUTOMATION_RUN_LOG_AFTER_STEP_RUN, function(AutomationRunLog $log) { $log->setData('test', 'value'); }); $automationRunLogs = $this->getLogsForAction(); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; expect($log->getData()['test'])->equals('value'); } public function testBadActionIntegrationsCannotDerailStepFromRunning() { $this->wp->addAction(Hooks::AUTOMATION_RUN_LOG_AFTER_STEP_RUN, function(AutomationRunLog $log) { throw new \Exception('bad integration'); }); $automationRunLogs = $this->getLogsForAction(); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; expect($log->getStatus())->equals(AutomationRunLog::STATUS_COMPLETE); } public function testItStoresAutomationRunAndStepIdsCorrectly() { $testAction = $this->getRegisteredTestAction(); $actionStep = new Step('action-step-id', Step::TYPE_ACTION, $testAction->getKey(), [], []); $automation = new Automation('test_automation', [$actionStep->getId() => $actionStep], new \WP_User()); $automation->setStatus(Automation::STATUS_ACTIVE); $automationId = $this->automationStorage->createAutomation($automation); // Reload to get additional data post-save $automation = $this->automationStorage->getAutomation($automationId); $this->assertInstanceOf(Automation::class, $automation); $automationRun = new AutomationRun($automationId, $automation->getVersionId(), 'trigger-key', []); $automationRunId = $this->automationRunStorage->createAutomationRun($automationRun); $this->stepHandler->handle([ 'automation_run_id' => $automationRunId, 'step_id' => 'action-step-id', ]); $log = $this->automationRunLogStorage->getLogsForAutomationRun($automationRunId)[0]; expect($log->getAutomationRunId())->equals($automationRunId); expect($log->getStepId())->equals('action-step-id'); } public function testItLogsCompletedStatusCorrectly(): void { $automationRunLogs = $this->getLogsForAction(); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; expect($log->getStatus())->equals('complete'); } public function testItSetsUpdatedAtTimestampAfterRunningSuccessfully(): void { $this->wp->addAction(Hooks::AUTOMATION_RUN_LOG_AFTER_STEP_RUN, function(AutomationRunLog $log) { expect($log->getUpdatedAt())->null(); }); $automationRunLogs = $this->getLogsForAction(); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; expect($log->getUpdatedAt())->isInstanceOf(\DateTimeImmutable::class); } public function testItSetsUpdatedAtTimestampAfterFailing(): void { $automationRunLogs = $this->getLogsForAction(function() { throw new \Exception('error'); }); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; expect($log->getUpdatedAt())->isInstanceOf(\DateTimeImmutable::class); } public function testItLogsFailedStatusCorrectly(): void { $automationRunLogs = $this->getLogsForAction(function() { throw new \Exception('error'); }); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; expect($log->getStatus())->equals('failed'); } public function testItIncludesErrorOnFailure(): void { $automationRunLogs = $this->getLogsForAction(function() { throw new \Exception('error', 12345); }); expect($automationRunLogs)->count(1); $log = $automationRunLogs[0]; $error = $log->getError(); expect($error['message'])->equals('error'); expect($error['code'])->equals(12345); expect($error['errorClass'])->equals('Exception'); expect($error['trace'])->array(); expect(count($error['trace']))->greaterThan(0); } private function getLogsForAction($callback = null) { if ($callback === null) { $callback = function() { return true; }; } $testAction = $this->getRegisteredTestAction($callback); $actionStep = new Step('action-step-id', Step::TYPE_ACTION, $testAction->getKey(), [], []); $automation = new Automation('test_automation', [$actionStep->getId() => $actionStep], new \WP_User()); $automation->setStatus(Automation::STATUS_ACTIVE); $automationId = $this->automationStorage->createAutomation($automation); // Reload to get additional data post-save $automation = $this->automationStorage->getAutomation($automationId); $this->assertInstanceOf(Automation::class, $automation); $automationRun = new AutomationRun($automationId, $automation->getVersionId(), 'trigger-key', []); $automationRunId = $this->automationRunStorage->createAutomationRun($automationRun); try { $this->stepHandler->handle([ 'automation_run_id' => $automationRunId, 'step_id' => 'action-step-id', ]); } catch (\Exception $e) { // allow exceptions so we can test failure states } return $this->automationRunLogStorage->getLogsForAutomationRun($automationRunId); } private function getRegisteredTestAction($callback = null) { if ($callback === null) { $callback = function() { return true; }; } $action = new TestAction(); $action->setCallback($callback); $this->registry->addAction($action); return $action; } }