diff --git a/src/ActivityStub.php b/src/ActivityStub.php index 0f5b850..b0f65eb 100644 --- a/src/ActivityStub.php +++ b/src/ActivityStub.php @@ -12,15 +12,6 @@ final class ActivityStub { - private $arguments; - - private function __construct( - protected $activity, - ...$arguments - ) { - $this->arguments = $arguments; - } - public static function all(iterable $promises): PromiseInterface { return all([...$promises]); @@ -39,30 +30,12 @@ public static function make($activity, ...$arguments): PromiseInterface WorkflowStub::setContext($context); return resolve(Y::unserialize($log->result)); } - $current = new self($activity, ...$arguments); - $current->activity()::dispatch( - $context->index, - $context->now, - $context->storedWorkflow, - ...$current->arguments() - ); + $activity::dispatch($context->index, $context->now, $context->storedWorkflow, ...$arguments); ++$context->index; WorkflowStub::setContext($context); - $deferred = new Deferred(); - return $deferred->promise(); } - - public function activity() - { - return $this->activity; - } - - public function arguments() - { - return $this->arguments; - } } diff --git a/src/Traits/AwaitWithTimeouts.php b/src/Traits/AwaitWithTimeouts.php new file mode 100644 index 0000000..64a4b20 --- /dev/null +++ b/src/Traits/AwaitWithTimeouts.php @@ -0,0 +1,60 @@ +storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + if (is_string($seconds)) { + $seconds = CarbonInterval::fromString($seconds)->totalSeconds; + } + + $result = $condition(); + + if ($result === true) { + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => Signal::class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + } + } + ++self::$context->index; + return resolve($result); + } + + return self::timer($seconds)->then(static fn ($completed): bool => ! $completed); + } +} diff --git a/src/Traits/Awaits.php b/src/Traits/Awaits.php new file mode 100644 index 0000000..4ff5e73 --- /dev/null +++ b/src/Traits/Awaits.php @@ -0,0 +1,58 @@ +storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + $result = $condition(); + + if ($result === true) { + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => Signal::class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + } + } + ++self::$context->index; + return resolve($result); + } + + ++self::$context->index; + $deferred = new Deferred(); + return $deferred->promise(); + } +} diff --git a/src/Traits/SideEffects.php b/src/Traits/SideEffects.php new file mode 100644 index 0000000..8dced83 --- /dev/null +++ b/src/Traits/SideEffects.php @@ -0,0 +1,51 @@ +storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + $result = $callable(); + + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => self::$context->storedWorkflow->class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + } + } + + ++self::$context->index; + return resolve($result); + } +} diff --git a/src/Traits/Timers.php b/src/Traits/Timers.php new file mode 100644 index 0000000..c3948d8 --- /dev/null +++ b/src/Traits/Timers.php @@ -0,0 +1,83 @@ +totalSeconds; + } + + if ($seconds <= 0) { + ++self::$context->index; + return resolve(true); + } + + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + $timer = self::$context->storedWorkflow->timers() + ->whereIndex(self::$context->index) + ->first(); + + if ($timer === null) { + $when = self::$context->now->copy() + ->addSeconds($seconds); + + if (! self::$context->replaying) { + $timer = self::$context->storedWorkflow->timers() + ->create([ + 'index' => self::$context->index, + 'stop_at' => $when, + ]); + } + } + + $result = $timer->stop_at + ->lessThanOrEqualTo(self::$context->now); + + if ($result === true) { + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } catch (QueryException $exception) { + // already logged + } + } + ++self::$context->index; + return resolve(true); + } + + if (! self::$context->replaying) { + Signal::dispatch(self::$context->storedWorkflow)->delay($timer->stop_at); + } + + ++self::$context->index; + $deferred = new Deferred(); + return $deferred->promise(); + } +} diff --git a/src/WorkflowStub.php b/src/WorkflowStub.php index 93050b2..77117af 100644 --- a/src/WorkflowStub.php +++ b/src/WorkflowStub.php @@ -4,21 +4,26 @@ namespace Workflow; -use Carbon\CarbonInterval; use Illuminate\Database\QueryException; use Illuminate\Support\Carbon; -use React\Promise\Deferred; -use React\Promise\PromiseInterface; -use function React\Promise\resolve; use ReflectionClass; use Workflow\Models\StoredWorkflow; use Workflow\Serializers\Y; use Workflow\States\WorkflowCompletedStatus; use Workflow\States\WorkflowFailedStatus; use Workflow\States\WorkflowPendingStatus; +use Workflow\Traits\Awaits; +use Workflow\Traits\AwaitWithTimeouts; +use Workflow\Traits\SideEffects; +use Workflow\Traits\Timers; final class WorkflowStub { + use Awaits; + use AwaitWithTimeouts; + use SideEffects; + use Timers; + private static ?\stdClass $context = null; private function __construct( @@ -94,121 +99,6 @@ public static function setContext($context): void self::$context = (object) $context; } - public static function await($condition): PromiseInterface - { - $result = $condition(); - - if ($result === true) { - if (! self::$context->replaying) { - try { - self::$context->storedWorkflow->logs() - ->create([ - 'index' => self::$context->index, - 'now' => self::$context->now, - 'class' => Signal::class, - 'result' => Y::serialize($result), - ]); - } catch (QueryException $exception) { - // already logged - } - } - ++self::$context->index; - return resolve(true); - } - - ++self::$context->index; - $deferred = new Deferred(); - return $deferred->promise(); - } - - public static function awaitWithTimeout($seconds, $condition): PromiseInterface - { - if (is_string($seconds)) { - $seconds = CarbonInterval::fromString($seconds)->totalSeconds; - } - - $result = $condition(); - - if ($result === true) { - if (! self::$context->replaying) { - try { - self::$context->storedWorkflow->logs() - ->create([ - 'index' => self::$context->index, - 'now' => self::$context->now, - 'class' => Signal::class, - 'result' => Y::serialize($result), - ]); - } catch (QueryException $exception) { - // already logged - } - } - ++self::$context->index; - return resolve($result); - } - - ++self::$context->index; - return self::timer($seconds)->then(static fn ($completed): bool => ! $completed); - } - - public static function timer($seconds): PromiseInterface - { - if (is_string($seconds)) { - $seconds = CarbonInterval::fromString($seconds)->totalSeconds; - } - - if ($seconds <= 0) { - ++self::$context->index; - return resolve(true); - } - - $timer = self::$context->storedWorkflow->timers() - ->whereIndex(self::$context->index) - ->first(); - - if ($timer === null) { - $when = self::$context->now->copy() - ->addSeconds($seconds); - - if (! self::$context->replaying) { - $timer = self::$context->storedWorkflow->timers() - ->create([ - 'index' => self::$context->index, - 'stop_at' => $when, - ]); - } - } - - $result = $timer->stop_at - ->lessThanOrEqualTo(self::$context->now); - - if ($result === true) { - if (! self::$context->replaying) { - try { - self::$context->storedWorkflow->logs() - ->create([ - 'index' => self::$context->index, - 'now' => self::$context->now, - 'class' => Signal::class, - 'result' => Y::serialize($result), - ]); - } catch (QueryException $exception) { - // already logged - } - } - ++self::$context->index; - return resolve($result); - } - - if (! self::$context->replaying) { - Signal::dispatch(self::$context->storedWorkflow)->delay($timer->stop_at); - } - - ++self::$context->index; - $deferred = new Deferred(); - return $deferred->promise(); - } - public static function now() { return self::getContext()->now; diff --git a/tests/Feature/SideEffectWorkflowTest.php b/tests/Feature/SideEffectWorkflowTest.php new file mode 100644 index 0000000..2e7b3ad --- /dev/null +++ b/tests/Feature/SideEffectWorkflowTest.php @@ -0,0 +1,35 @@ +start(); + + while ($workflow->running()); + + $this->assertSame(WorkflowCompletedStatus::class, $workflow->status()); + $this->assertSame('workflow', $workflow->output()); + $this->assertSame( + [TestActivity::class, TestOtherActivity::class, TestOtherActivity::class, TestSideEffectWorkflow::class], + $workflow->logs() + ->pluck('class') + ->sort() + ->values() + ->toArray() + ); + } +} diff --git a/tests/Fixtures/TestSideEffectWorkflow.php b/tests/Fixtures/TestSideEffectWorkflow.php new file mode 100644 index 0000000..97903c6 --- /dev/null +++ b/tests/Fixtures/TestSideEffectWorkflow.php @@ -0,0 +1,44 @@ + random_int(PHP_INT_MIN, PHP_INT_MAX)); + + $badSideEffect = random_int(PHP_INT_MIN, PHP_INT_MAX); + + $result = yield ActivityStub::make(TestActivity::class); + + $otherResult1 = yield ActivityStub::make(TestOtherActivity::class, $sideEffect); + + $otherResult2 = yield ActivityStub::make(TestOtherActivity::class, $badSideEffect); + + if ($sideEffect !== $otherResult1) { + throw new Exception( + 'These side effects should match because it was properly wrapped in WorkflowStub::sideEffect().' + ); + } + + if ($badSideEffect === $otherResult2) { + throw new Exception( + 'These side effects should not match because it was not wrapped in WorkflowStub::sideEffect().' + ); + } + + return 'workflow'; + } +} diff --git a/tests/Unit/ActivityStubTest.php b/tests/Unit/ActivityStubTest.php new file mode 100644 index 0000000..54bc8fc --- /dev/null +++ b/tests/Unit/ActivityStubTest.php @@ -0,0 +1,91 @@ +id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + ActivityStub::make(TestActivity::class) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(WorkflowPendingStatus::class, $workflow->status()); + $this->assertNull($workflow->output()); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestActivity::class, + 'result' => Y::serialize('activity'), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestActivity::class, + 'result' => Y::serialize('test'), + ]); + + ActivityStub::make(TestActivity::class) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame('test', $result); + } + + public function testAll(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestActivity::class, + 'result' => Y::serialize('test'), + ]); + + ActivityStub::all([ActivityStub::make(TestActivity::class)]) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(['test'], $result); + } +} diff --git a/tests/Unit/Traits/AwaitWithTimeoutsTest.php b/tests/Unit/Traits/AwaitWithTimeoutsTest.php new file mode 100644 index 0000000..e4c5b95 --- /dev/null +++ b/tests/Unit/Traits/AwaitWithTimeoutsTest.php @@ -0,0 +1,117 @@ +id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + WorkflowStub::awaitWithTimeout(60, static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testStoresResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testResolvesConflictingResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::awaitWithTimeout('1 minute', static function () use ($workflow) { + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + return true; + }) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(false, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + } +} diff --git a/tests/Unit/Traits/AwaitsTest.php b/tests/Unit/Traits/AwaitsTest.php new file mode 100644 index 0000000..a1da364 --- /dev/null +++ b/tests/Unit/Traits/AwaitsTest.php @@ -0,0 +1,103 @@ +id()); + + WorkflowStub::await(static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testStoresResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::await(static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + + WorkflowStub::await(static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testResolvesConflictingResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::await(static function () use ($workflow) { + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + return true; + }) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(false, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + } +} diff --git a/tests/Unit/Traits/SideEffectsTest.php b/tests/Unit/Traits/SideEffectsTest.php new file mode 100644 index 0000000..e1794ea --- /dev/null +++ b/tests/Unit/Traits/SideEffectsTest.php @@ -0,0 +1,89 @@ +id()); + + WorkflowStub::sideEffect(static fn () => 'test') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame('test', $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + + WorkflowStub::sideEffect(static fn () => '') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame('test', $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + } + + public function testResolvesConflictingResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::sideEffect(static function () use ($workflow) { + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + return ''; + }) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame('test', $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + } +} diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php new file mode 100644 index 0000000..8c4571b --- /dev/null +++ b/tests/Unit/Traits/TimersTest.php @@ -0,0 +1,141 @@ +id()); + + WorkflowStub::timer(0) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertTrue($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testCreatesTimer(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + WorkflowStub::timer('1 minute') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_timers', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'stop_at' => WorkflowStub::now()->addMinute(), + ]); + } + + public function testDefersIfNotElapsed(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now() + ->addHour(), + ]); + + WorkflowStub::timer(60) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + + WorkflowStub::timer('1 minute', static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testStoresResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now(), + ]); + + WorkflowStub::timer('1 minute') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now(), + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => now(), + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + + WorkflowStub::timer('1 minute', static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } +} diff --git a/tests/Unit/WorkflowStubTest.php b/tests/Unit/WorkflowStubTest.php index b9fe254..630a12a 100644 --- a/tests/Unit/WorkflowStubTest.php +++ b/tests/Unit/WorkflowStubTest.php @@ -151,9 +151,13 @@ public function testAwaitWithTimeoutTimedout(): void $workflow->cancel(); while (! $workflow->isCanceled()); - $workflow = WorkflowStub::load($workflow->id()); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertSame(1, WorkflowStub::getContext()->index); - $this->assertSame(0, WorkflowStub::getContext()->index); + $workflow = WorkflowStub::load($workflow->id()); + $context = WorkflowStub::getContext(); + $context->index = 1; + WorkflowStub::setContext($context); $promise = WorkflowStub::awaitWithTimeout('1 minute', static fn () => false); @@ -162,6 +166,7 @@ public function testAwaitWithTimeoutTimedout(): void $workflow = WorkflowStub::load($workflow->id()); $context = WorkflowStub::getContext(); + $context->index = 1; $context->now = $context->now->addMinute(); WorkflowStub::setContext($context);
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: