summaryrefslogtreecommitdiff
path: root/tests/temporal
diff options
context:
space:
mode:
authorValery Piashchynski <[email protected]>2021-01-25 22:47:02 +0300
committerValery Piashchynski <[email protected]>2021-01-25 22:47:02 +0300
commit43071e43a0743ff8c7913bba7819952962124355 (patch)
treee3b61113d3c0d28f972c71592af8b2f708994167 /tests/temporal
parent5fd1168c687040ca7d72f4727ee1aec753d3f258 (diff)
Initial commit of the Temporal plugins set
Diffstat (limited to 'tests/temporal')
-rw-r--r--tests/temporal/Activity/HeartBeatActivity.php58
-rw-r--r--tests/temporal/Activity/SimpleActivity.php63
-rw-r--r--tests/temporal/Client/StartNewWorkflow.php23
-rw-r--r--tests/temporal/DTO/Message.php14
-rw-r--r--tests/temporal/DTO/User.php15
-rw-r--r--tests/temporal/Workflow/ActivityStubWorkflow.php39
-rw-r--r--tests/temporal/Workflow/AggregatedWorkflow.php30
-rw-r--r--tests/temporal/Workflow/AsyncActivityWorkflow.php28
-rw-r--r--tests/temporal/Workflow/BinaryWorkflow.php21
-rw-r--r--tests/temporal/Workflow/CancelSignalledChildWorkflow.php57
-rw-r--r--tests/temporal/Workflow/CanceledHeartbeatWorkflow.php29
-rw-r--r--tests/temporal/Workflow/CancelledMidflightWorkflow.php47
-rw-r--r--tests/temporal/Workflow/CancelledNestedWorkflow.php72
-rw-r--r--tests/temporal/Workflow/CancelledScopeWorkflow.php39
-rw-r--r--tests/temporal/Workflow/CancelledSingleScopeWorkflow.php55
-rw-r--r--tests/temporal/Workflow/CancelledWithCompensationWorkflow.php79
-rw-r--r--tests/temporal/Workflow/CancelledWorkflow.php31
-rw-r--r--tests/temporal/Workflow/ChainedWorkflow.php31
-rw-r--r--tests/temporal/Workflow/ChildStubWorkflow.php30
-rw-r--r--tests/temporal/Workflow/ComplexExceptionalWorkflow.php26
-rw-r--r--tests/temporal/Workflow/ContinuableWorkflow.php38
-rw-r--r--tests/temporal/Workflow/EmptyWorkflow.php16
-rw-r--r--tests/temporal/Workflow/ExceptionalActivityWorkflow.php25
-rw-r--r--tests/temporal/Workflow/ExceptionalWorkflow.php18
-rw-r--r--tests/temporal/Workflow/FailedHeartbeatWorkflow.php30
-rw-r--r--tests/temporal/Workflow/LoopWithSignalCoroutinesWorkflow.php55
-rw-r--r--tests/temporal/Workflow/LoopWorkflow.php51
-rw-r--r--tests/temporal/Workflow/ParallelScopesWorkflow.php36
-rw-r--r--tests/temporal/Workflow/PeriodicWorkflow.php19
-rw-r--r--tests/temporal/Workflow/ProtoPayloadWorkflow.php33
-rw-r--r--tests/temporal/Workflow/QueryWorkflow.php41
-rw-r--r--tests/temporal/Workflow/RuntimeSignalWorkflow.php31
-rw-r--r--tests/temporal/Workflow/SagaWorkflow.php54
-rw-r--r--tests/temporal/Workflow/SideEffectWorkflow.php30
-rw-r--r--tests/temporal/Workflow/SignalChildViaStubWorkflow.php25
-rw-r--r--tests/temporal/Workflow/SimpleDTOWorkflow.php35
-rw-r--r--tests/temporal/Workflow/SimpleHeartbeatWorkflow.php25
-rw-r--r--tests/temporal/Workflow/SimpleSignalledWorkflow.php30
-rw-r--r--tests/temporal/Workflow/SimpleSignalledWorkflowWithSleep.php34
-rw-r--r--tests/temporal/Workflow/SimpleWorkflow.php31
-rw-r--r--tests/temporal/Workflow/TimerWorkflow.php27
-rw-r--r--tests/temporal/Workflow/WaitWorkflow.php33
-rw-r--r--tests/temporal/Workflow/WithChildStubWorkflow.php20
-rw-r--r--tests/temporal/Workflow/WithChildWorkflow.php25
-rw-r--r--tests/temporal/Workflow/WorkflowWithSequence.php30
-rw-r--r--tests/temporal/Workflow/WorkflowWithSignalledSteps.php51
46 files changed, 1630 insertions, 0 deletions
diff --git a/tests/temporal/Activity/HeartBeatActivity.php b/tests/temporal/Activity/HeartBeatActivity.php
new file mode 100644
index 00000000..acf4a451
--- /dev/null
+++ b/tests/temporal/Activity/HeartBeatActivity.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Temporal\Tests\Activity;
+
+use Temporal\Activity;
+use Temporal\Activity\ActivityInterface;
+use Temporal\Activity\ActivityMethod;
+use Temporal\Roadrunner\Internal\Error;
+
+#[ActivityInterface(prefix: "HeartBeatActivity.")]
+class HeartBeatActivity
+{
+ #[ActivityMethod]
+ public function doSomething(
+ int $value
+ ): string {
+ Activity::heartbeat(['value' => $value]);
+ sleep($value);
+ return 'OK';
+ }
+
+ #[ActivityMethod]
+ public function slow(
+ string $value
+ ): string {
+ for ($i = 0; $i < 5; $i++) {
+ Activity::heartbeat(['value' => $i]);
+ sleep(1);
+ }
+
+ return 'OK';
+ }
+
+ #[ActivityMethod]
+ public function something(
+ string $value
+ ): string {
+ Activity::heartbeat(['value' => $value]);
+ sleep($value);
+ return 'OK';
+ }
+
+ #[ActivityMethod]
+ public function failedActivity(
+ int $value
+ ): string {
+ Activity::heartbeat(['value' => $value]);
+ if (Activity::getInfo()->attempt === 1) {
+ throw new \Error("failed");
+ }
+
+ if (!is_array(Activity::getHeartbeatDetails())) {
+ throw new \Error("no heartbeat details");
+ }
+
+ return 'OK!';
+ }
+} \ No newline at end of file
diff --git a/tests/temporal/Activity/SimpleActivity.php b/tests/temporal/Activity/SimpleActivity.php
new file mode 100644
index 00000000..576b126e
--- /dev/null
+++ b/tests/temporal/Activity/SimpleActivity.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Temporal\Tests\Activity;
+
+use Temporal\Activity\ActivityInterface;
+use Temporal\Activity\ActivityMethod;
+use Temporal\Api\Common\V1\WorkflowExecution;
+use Temporal\DataConverter\Bytes;
+use Temporal\Tests\DTO\Message;
+use Temporal\Tests\DTO\User;
+
+#[ActivityInterface(prefix: "SimpleActivity.")]
+class SimpleActivity
+{
+ #[ActivityMethod]
+ public function echo(
+ string $input
+ ): string {
+ return strtoupper($input);
+ }
+
+ #[ActivityMethod]
+ public function lower(
+ string $input
+ ): string {
+ return strtolower($input);
+ }
+
+ #[ActivityMethod]
+ public function greet(
+ User $user
+ ): Message {
+ return new Message(sprintf("Hello %s <%s>", $user->name, $user->email));
+ }
+
+ #[ActivityMethod]
+ public function slow(
+ string $input
+ ): string {
+ sleep(2);
+
+ return strtolower($input);
+ }
+
+ #[ActivityMethod]
+ public function sha512(
+ Bytes $input
+ ): string {
+ return hash("sha512", ($input->getData()));
+ }
+
+ public function updateRunID(WorkflowExecution $e): WorkflowExecution
+ {
+ $e->setRunId('updated');
+ return $e;
+ }
+
+ #[ActivityMethod]
+ public function fail()
+ {
+ throw new \Error("failed activity");
+ }
+} \ No newline at end of file
diff --git a/tests/temporal/Client/StartNewWorkflow.php b/tests/temporal/Client/StartNewWorkflow.php
new file mode 100644
index 00000000..67bc1d01
--- /dev/null
+++ b/tests/temporal/Client/StartNewWorkflow.php
@@ -0,0 +1,23 @@
+<?php
+
+
+namespace Temporal\Tests\Client;
+
+use Temporal\Client;
+use Temporal\Tests\Workflow\SimpleDTOWorkflow;
+
+use function Symfony\Component\String\s;
+
+class StartNewWorkflow
+{
+ private $stub;
+
+ public function __construct(Client\ClientInterface $client)
+ {
+ $this->stub = $client->newWorkflowStub(SimpleDTOWorkflow::class);
+ }
+
+ public function __invoke()
+ {
+ }
+}
diff --git a/tests/temporal/DTO/Message.php b/tests/temporal/DTO/Message.php
new file mode 100644
index 00000000..61703fe8
--- /dev/null
+++ b/tests/temporal/DTO/Message.php
@@ -0,0 +1,14 @@
+<?php
+
+
+namespace Temporal\Tests\DTO;
+
+class Message
+{
+ public string $message;
+
+ public function __construct(string $message)
+ {
+ $this->message = $message;
+ }
+} \ No newline at end of file
diff --git a/tests/temporal/DTO/User.php b/tests/temporal/DTO/User.php
new file mode 100644
index 00000000..cefea137
--- /dev/null
+++ b/tests/temporal/DTO/User.php
@@ -0,0 +1,15 @@
+<?php
+
+
+namespace Temporal\Tests\DTO;
+
+use Temporal\Internal\Marshaller\Meta\Marshal;
+
+class User
+{
+ #[Marshal(name: "Name")]
+ public string $name;
+
+ #[Marshal(name: "Email")]
+ public string $email;
+} \ No newline at end of file
diff --git a/tests/temporal/Workflow/ActivityStubWorkflow.php b/tests/temporal/Workflow/ActivityStubWorkflow.php
new file mode 100644
index 00000000..58dcdafb
--- /dev/null
+++ b/tests/temporal/Workflow/ActivityStubWorkflow.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ActivityStubWorkflow
+{
+ #[WorkflowMethod(name: 'ActivityStubWorkflow')]
+ public function handler(
+ string $input
+ ) {
+ // typed stub
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $result = [];
+ $result[] = yield $simple->echo($input);
+
+ try {
+ $simple->undefined($input);
+ } catch (\BadMethodCallException $e) {
+ $result[] = 'invalid method call';
+ }
+
+ // untyped stub
+ $untyped = Workflow::newUntypedActivityStub(ActivityOptions::new()->withStartToCloseTimeout(1));
+
+ $result[] = yield $untyped->execute('SimpleActivity.echo', ['untyped']);
+
+ return $result;
+ }
+}
diff --git a/tests/temporal/Workflow/AggregatedWorkflow.php b/tests/temporal/Workflow/AggregatedWorkflow.php
new file mode 100644
index 00000000..3299179e
--- /dev/null
+++ b/tests/temporal/Workflow/AggregatedWorkflow.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\SignalMethod;
+use Temporal\Workflow\WorkflowInterface;
+use Temporal\Workflow\WorkflowMethod;
+
+#[WorkflowInterface]
+class AggregatedWorkflow
+{
+ private array $values = [];
+
+ #[SignalMethod]
+ public function addValue(
+ string $value
+ ) {
+ $this->values[] = $value;
+ }
+
+ #[WorkflowMethod(name: 'AggregatedWorkflow')]
+ public function run(
+ int $count
+ ) {
+ yield Workflow::await(fn() => count($this->values) === $count);
+
+ return $this->values;
+ }
+}
diff --git a/tests/temporal/Workflow/AsyncActivityWorkflow.php b/tests/temporal/Workflow/AsyncActivityWorkflow.php
new file mode 100644
index 00000000..79e45dfb
--- /dev/null
+++ b/tests/temporal/Workflow/AsyncActivityWorkflow.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityCancellationType;
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class AsyncActivityWorkflow
+{
+ #[WorkflowMethod(name: 'AsyncActivityWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(20)
+ ->withCancellationType(ActivityCancellationType::WAIT_CANCELLATION_COMPLETED)
+ );
+
+ return yield $simple->external();
+ }
+}
diff --git a/tests/temporal/Workflow/BinaryWorkflow.php b/tests/temporal/Workflow/BinaryWorkflow.php
new file mode 100644
index 00000000..ed1952ad
--- /dev/null
+++ b/tests/temporal/Workflow/BinaryWorkflow.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\DataConverter\Bytes;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class BinaryWorkflow
+{
+ #[WorkflowMethod(name: 'BinaryWorkflow')]
+ public function handler(
+ Bytes $input
+ ): iterable {
+ $opts = ActivityOptions::new()->withStartToCloseTimeout(5);
+
+ return yield Workflow::executeActivity('SimpleActivity.sha512', [$input], $opts);
+ }
+}
diff --git a/tests/temporal/Workflow/CancelSignalledChildWorkflow.php b/tests/temporal/Workflow/CancelSignalledChildWorkflow.php
new file mode 100644
index 00000000..e2e43efa
--- /dev/null
+++ b/tests/temporal/Workflow/CancelSignalledChildWorkflow.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use React\Promise\Deferred;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class CancelSignalledChildWorkflow
+{
+ private array $status = [];
+
+ #[Workflow\QueryMethod(name: 'getStatus')]
+ public function getStatus(): array
+ {
+ return $this->status;
+ }
+
+ #[WorkflowMethod(name: 'CancelSignalledChildWorkflow')]
+ public function handler()
+ {
+ // typed stub
+ $simple = Workflow::newChildWorkflowStub(SimpleSignalledWorkflow::class);
+
+ $waitSignalled = new Deferred();
+
+ $this->status[] = 'start';
+
+ // start execution
+ $scope = Workflow::newCancellationScope(
+ function () use ($simple, $waitSignalled) {
+ $call = $simple->handler();
+ $this->status[] = 'child started';
+
+ yield $simple->add(8);
+ $this->status[] = 'child signalled';
+ $waitSignalled->resolve();
+
+ return yield $call;
+ }
+ );
+
+ // only cancel scope when signal dispatched
+ yield $waitSignalled;
+ $scope->cancel();
+ $this->status[] = 'scope cancelled';
+
+ try {
+ return yield $scope;
+ } catch (\Throwable $e) {
+ $this->status[] = 'process done';
+
+ return 'cancelled ok';
+ }
+ }
+}
diff --git a/tests/temporal/Workflow/CanceledHeartbeatWorkflow.php b/tests/temporal/Workflow/CanceledHeartbeatWorkflow.php
new file mode 100644
index 00000000..6b463192
--- /dev/null
+++ b/tests/temporal/Workflow/CanceledHeartbeatWorkflow.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityCancellationType;
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\HeartBeatActivity;
+
+#[Workflow\WorkflowInterface]
+class CanceledHeartbeatWorkflow
+{
+ #[WorkflowMethod(name: 'CanceledHeartbeatWorkflow')]
+ public function handler(): iterable
+ {
+ $act = Workflow::newActivityStub(
+ HeartBeatActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(50)
+ ->withCancellationType(ActivityCancellationType::WAIT_CANCELLATION_COMPLETED)
+ ->withHeartbeatTimeout(1)
+ );
+
+ return yield $act->slow('test');
+ }
+}
diff --git a/tests/temporal/Workflow/CancelledMidflightWorkflow.php b/tests/temporal/Workflow/CancelledMidflightWorkflow.php
new file mode 100644
index 00000000..ea799ce1
--- /dev/null
+++ b/tests/temporal/Workflow/CancelledMidflightWorkflow.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class CancelledMidflightWorkflow
+{
+ private array $status = [];
+
+ #[Workflow\QueryMethod(name: 'getStatus')]
+ public function getStatus(): array
+ {
+ return $this->status;
+ }
+
+ #[WorkflowMethod(name: 'CancelledMidflightWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $this->status[] = 'start';
+
+ $scope = Workflow::newCancellationScope(
+ function () use ($simple) {
+ $this->status[] = 'in scope';
+ $simple->slow('1');
+ }
+ )->onCancel(
+ function () {
+ $this->status[] = 'on cancel';
+ }
+ );
+
+ $scope->cancel();
+ $this->status[] = 'done cancel';
+
+ return 'OK';
+ }
+}
diff --git a/tests/temporal/Workflow/CancelledNestedWorkflow.php b/tests/temporal/Workflow/CancelledNestedWorkflow.php
new file mode 100644
index 00000000..0c82f761
--- /dev/null
+++ b/tests/temporal/Workflow/CancelledNestedWorkflow.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Exception\Failure\CanceledFailure;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class CancelledNestedWorkflow
+{
+ private array $status = [];
+
+ #[Workflow\QueryMethod(name: 'getStatus')]
+ public function getStatus(): array
+ {
+ return $this->status;
+ }
+
+ #[WorkflowMethod(name: 'CancelledNestedWorkflow')]
+ public function handler()
+ {
+ $this->status[] = 'begin';
+ try {
+ yield Workflow::newCancellationScope(
+ function () {
+ $this->status[] = 'first scope';
+
+ $scope = Workflow::newCancellationScope(
+ function () {
+ $this->status[] = 'second scope';
+
+ try {
+ yield Workflow::timer(2);
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'second scope cancelled';
+ throw $e;
+ }
+
+ $this->status[] = 'second scope done';
+ }
+ )->onCancel(
+ function () {
+ $this->status[] = 'close second scope';
+ }
+ );
+
+ try {
+ yield Workflow::timer(1);
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'first scope cancelled';
+ throw $e;
+ }
+
+ $this->status[] = 'first scope done';
+
+ yield $scope;
+ }
+ )->onCancel(
+ function () {
+ $this->status[] = 'close first scope';
+ }
+ );
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'close process';
+
+ return 'CANCELLED';
+ }
+
+ return 'OK';
+ }
+}
diff --git a/tests/temporal/Workflow/CancelledScopeWorkflow.php b/tests/temporal/Workflow/CancelledScopeWorkflow.php
new file mode 100644
index 00000000..50e0992f
--- /dev/null
+++ b/tests/temporal/Workflow/CancelledScopeWorkflow.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class CancelledScopeWorkflow
+{
+ #[WorkflowMethod(name: 'CancelledScopeWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $cancelled = 'not';
+
+ $scope = Workflow::newCancellationScope(
+ function () use ($simple) {
+ yield Workflow::timer(2);
+ yield $simple->slow('hello');
+ }
+ )->onCancel(
+ function () use (&$cancelled) {
+ $cancelled = 'yes';
+ }
+ );
+
+ yield Workflow::timer(1);
+ $scope->cancel();
+
+ return $cancelled;
+ }
+}
diff --git a/tests/temporal/Workflow/CancelledSingleScopeWorkflow.php b/tests/temporal/Workflow/CancelledSingleScopeWorkflow.php
new file mode 100644
index 00000000..5fe8d3d8
--- /dev/null
+++ b/tests/temporal/Workflow/CancelledSingleScopeWorkflow.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Exception\Failure\CanceledFailure;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class CancelledSingleScopeWorkflow
+{
+ private array $status = [];
+
+ #[Workflow\QueryMethod(name: 'getStatus')]
+ public function getStatus(): array
+ {
+ return $this->status;
+ }
+
+ #[WorkflowMethod(name: 'CancelledSingleScopeWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(5)
+ );
+
+ $this->status[] = 'start';
+ try {
+ yield Workflow::newCancellationScope(
+ function () use ($simple) {
+ try {
+ $this->status[] = 'in scope';
+ yield $simple->slow('1');
+ } catch (CanceledFailure $e) {
+ // after process is complete, do not use for business logic
+ $this->status[] = 'captured in scope';
+ throw $e;
+ }
+ }
+ )->onCancel(
+ function () {
+ $this->status[] = 'on cancel';
+ }
+ );
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'captured in process';
+ }
+
+ return 'OK';
+ }
+}
diff --git a/tests/temporal/Workflow/CancelledWithCompensationWorkflow.php b/tests/temporal/Workflow/CancelledWithCompensationWorkflow.php
new file mode 100644
index 00000000..2074aac1
--- /dev/null
+++ b/tests/temporal/Workflow/CancelledWithCompensationWorkflow.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Exception\Failure\CanceledFailure;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class CancelledWithCompensationWorkflow
+{
+ private array $status = [];
+
+ #[Workflow\QueryMethod(name: 'getStatus')]
+ public function getStatus(): array
+ {
+ return $this->status;
+ }
+
+ #[WorkflowMethod(name: 'CancelledWithCompensationWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ // waits for 2 seconds
+ $slow = $simple->slow('DOING SLOW ACTIVITY');
+
+ try {
+ $this->status[] = 'yield';
+ $result = yield $slow;
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'rollback';
+
+ try {
+ // must fail again
+ $result = yield $slow;
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'captured retry';
+ }
+
+ try {
+ // fail since on cancelled context
+ $result = yield $simple->echo('echo must fail');
+ } catch (CanceledFailure $e) {
+ $this->status[] = 'captured promise on cancelled';
+ }
+
+ $scope = Workflow::newDetachedCancellationScope(
+ function () use ($simple) {
+ $this->status[] = 'START rollback';
+
+ $second = yield $simple->echo('rollback');
+
+ $this->status[] = sprintf("RESULT (%s)", $second);
+
+ if ($second !== 'ROLLBACK') {
+ $this->status[] = 'FAIL rollback';
+ return 'failed to compensate ' . $second;
+ }
+ $this->status[] = 'DONE rollback';
+
+ return 'OK';
+ }
+ );
+
+ $this->status[] = 'WAIT ROLLBACK';
+ $result = yield $scope;
+ $this->status[] = 'COMPLETE rollback';
+ }
+
+ $this->status[] = 'result: ' . $result;
+ return $result;
+ }
+}
diff --git a/tests/temporal/Workflow/CancelledWorkflow.php b/tests/temporal/Workflow/CancelledWorkflow.php
new file mode 100644
index 00000000..be9f7542
--- /dev/null
+++ b/tests/temporal/Workflow/CancelledWorkflow.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Exception\Failure\CanceledFailure;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class CancelledWorkflow
+{
+ #[WorkflowMethod(name: 'CancelledWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ // waits for 2 seconds
+ $slow = $simple->slow('DOING SLOW ACTIVITY');
+
+ try {
+ return yield $slow;
+ } catch (CanceledFailure $e) {
+ return "CANCELLED";
+ }
+ }
+}
diff --git a/tests/temporal/Workflow/ChainedWorkflow.php b/tests/temporal/Workflow/ChainedWorkflow.php
new file mode 100644
index 00000000..ba9c8f96
--- /dev/null
+++ b/tests/temporal/Workflow/ChainedWorkflow.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ChainedWorkflow
+{
+ #[WorkflowMethod(name: 'ChainedWorkflow')]
+ public function handler(string $input): iterable
+ {
+ $opts = ActivityOptions::new()->withStartToCloseTimeout(5);
+
+ return yield Workflow::executeActivity(
+ 'SimpleActivity.echo',
+ [$input],
+ $opts
+ )->then(function ($result) use ($opts) {
+ return Workflow::executeActivity(
+ 'SimpleActivity.lower',
+ ['Result:' . $result],
+ $opts
+ );
+ });
+ }
+}
diff --git a/tests/temporal/Workflow/ChildStubWorkflow.php b/tests/temporal/Workflow/ChildStubWorkflow.php
new file mode 100644
index 00000000..608962c2
--- /dev/null
+++ b/tests/temporal/Workflow/ChildStubWorkflow.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ChildStubWorkflow
+{
+ #[WorkflowMethod(name: 'ChildStubWorkflow')]
+ public function handler(
+ string $input
+ ) {
+ // typed stub
+ $simple = Workflow::newChildWorkflowStub(SimpleWorkflow::class);
+
+ $result = [];
+ $result[] = yield $simple->handler($input);
+
+ // untyped
+ $untyped = Workflow::newUntypedChildWorkflowStub('SimpleWorkflow');
+ $result[] = yield $untyped->execute(['untyped']);
+
+ $execution = yield $untyped->getExecution();
+ assert($execution instanceof Workflow\WorkflowExecution);
+
+ return $result;
+ }
+}
diff --git a/tests/temporal/Workflow/ComplexExceptionalWorkflow.php b/tests/temporal/Workflow/ComplexExceptionalWorkflow.php
new file mode 100644
index 00000000..bf65ccb2
--- /dev/null
+++ b/tests/temporal/Workflow/ComplexExceptionalWorkflow.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Common\RetryOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ComplexExceptionalWorkflow
+{
+ #[WorkflowMethod(name: 'ComplexExceptionalWorkflow')]
+ public function handler()
+ {
+ $child = Workflow::newChildWorkflowStub(
+ ExceptionalActivityWorkflow::class,
+ Workflow\ChildWorkflowOptions::new()->withRetryOptions(
+ (new RetryOptions())->withMaximumAttempts(1)
+ )
+ );
+
+ return yield $child->handler();
+ }
+}
diff --git a/tests/temporal/Workflow/ContinuableWorkflow.php b/tests/temporal/Workflow/ContinuableWorkflow.php
new file mode 100644
index 00000000..78411414
--- /dev/null
+++ b/tests/temporal/Workflow/ContinuableWorkflow.php
@@ -0,0 +1,38 @@
+<?php
+
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class ContinuableWorkflow
+{
+ #[WorkflowMethod(name: 'ContinuableWorkflow')]
+ public function handler(
+ int $generation
+ ) {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ if ($generation > 5) {
+ // complete
+ return "OK" . $generation;
+ }
+
+ if ($generation !== 1) {
+ assert(!empty(Workflow::getInfo()->continuedExecutionRunId));
+ }
+
+ for ($i = 0; $i < $generation; $i++) {
+ yield $simple->echo((string)$generation);
+ }
+
+ return Workflow::continueAsNew('ContinuableWorkflow', [++$generation]);
+ }
+}
diff --git a/tests/temporal/Workflow/EmptyWorkflow.php b/tests/temporal/Workflow/EmptyWorkflow.php
new file mode 100644
index 00000000..57fb5e65
--- /dev/null
+++ b/tests/temporal/Workflow/EmptyWorkflow.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Workflow;
+
+#[Workflow\WorkflowInterface]
+class EmptyWorkflow
+{
+ #[WorkflowMethod]
+ public function handler()
+ {
+ return 42;
+ }
+}
diff --git a/tests/temporal/Workflow/ExceptionalActivityWorkflow.php b/tests/temporal/Workflow/ExceptionalActivityWorkflow.php
new file mode 100644
index 00000000..e0ed0005
--- /dev/null
+++ b/tests/temporal/Workflow/ExceptionalActivityWorkflow.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Common\RetryOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ExceptionalActivityWorkflow
+{
+ #[WorkflowMethod(name: 'ExceptionalActivityWorkflow')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ ->withRetryOptions((new RetryOptions())->withMaximumAttempts(1))
+ );
+
+ return yield $simple->fail();
+ }
+}
diff --git a/tests/temporal/Workflow/ExceptionalWorkflow.php b/tests/temporal/Workflow/ExceptionalWorkflow.php
new file mode 100644
index 00000000..9a3e907f
--- /dev/null
+++ b/tests/temporal/Workflow/ExceptionalWorkflow.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ExceptionalWorkflow
+{
+ #[WorkflowMethod(name: 'ExceptionalWorkflow')]
+ public function handler()
+ {
+ throw new \RuntimeException("workflow error");
+ }
+}
diff --git a/tests/temporal/Workflow/FailedHeartbeatWorkflow.php b/tests/temporal/Workflow/FailedHeartbeatWorkflow.php
new file mode 100644
index 00000000..e857f100
--- /dev/null
+++ b/tests/temporal/Workflow/FailedHeartbeatWorkflow.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Common\RetryOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\HeartBeatActivity;
+
+#[Workflow\WorkflowInterface]
+class FailedHeartbeatWorkflow
+{
+ #[WorkflowMethod(name: 'FailedHeartbeatWorkflow')]
+ public function handler(
+ int $iterations
+ ): iterable {
+ $act = Workflow::newActivityStub(
+ HeartBeatActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(50)
+ // will fail on first attempt
+ ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(2))
+ );
+
+ return yield $act->failedActivity($iterations);
+ }
+}
diff --git a/tests/temporal/Workflow/LoopWithSignalCoroutinesWorkflow.php b/tests/temporal/Workflow/LoopWithSignalCoroutinesWorkflow.php
new file mode 100644
index 00000000..c389fd78
--- /dev/null
+++ b/tests/temporal/Workflow/LoopWithSignalCoroutinesWorkflow.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\SignalMethod;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class LoopWithSignalCoroutinesWorkflow
+{
+ private array $values = [];
+ private array $result = [];
+ private $simple;
+
+ public function __construct()
+ {
+ $this->simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+ }
+
+ #[SignalMethod]
+ public function addValue(
+ string $value
+ ) {
+ $value = yield $this->simple->prefix('in signal ', $value);
+ $value = yield $this->simple->prefix('in signal 2 ', $value);
+
+ $this->values[] = $value;
+ }
+
+ #[WorkflowMethod(name: 'LoopWithSignalCoroutinesWorkflow')]
+ public function run(
+ int $count
+ ) {
+ while (true) {
+ yield Workflow::await(fn() => $this->values !== []);
+ $value = array_shift($this->values);
+
+ // uppercases
+ $this->result[] = yield $this->simple->echo($value);
+
+ if (count($this->result) === $count) {
+ break;
+ }
+ }
+
+ asort($this->result);
+ return array_values($this->result);
+ }
+}
diff --git a/tests/temporal/Workflow/LoopWorkflow.php b/tests/temporal/Workflow/LoopWorkflow.php
new file mode 100644
index 00000000..97d7a3aa
--- /dev/null
+++ b/tests/temporal/Workflow/LoopWorkflow.php
@@ -0,0 +1,51 @@
+<?php
+
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\SignalMethod;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class LoopWorkflow
+{
+ private array $values = [];
+ private array $result = [];
+ private $simple;
+
+ public function __construct()
+ {
+ $this->simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+ }
+
+ #[SignalMethod]
+ public function addValue(
+ string $value
+ ) {
+ $this->values[] = $value;
+ }
+
+ #[WorkflowMethod(name: 'LoopWorkflow')]
+ public function run(
+ int $count
+ ) {
+ while (true) {
+ yield Workflow::await(fn() => $this->values !== []);
+ $value = array_shift($this->values);
+
+ $this->result[] = yield $this->simple->echo($value);
+
+ if (count($this->result) === $count) {
+ break;
+ }
+ }
+
+ return $this->result;
+ }
+}
diff --git a/tests/temporal/Workflow/ParallelScopesWorkflow.php b/tests/temporal/Workflow/ParallelScopesWorkflow.php
new file mode 100644
index 00000000..8a2303f4
--- /dev/null
+++ b/tests/temporal/Workflow/ParallelScopesWorkflow.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Promise;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class ParallelScopesWorkflow
+{
+ #[WorkflowMethod(name: 'ParallelScopesWorkflow')]
+ public function handler(string $input)
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $a = Workflow::newCancellationScope(function () use ($simple, $input) {
+ return yield $simple->echo($input);
+ });
+
+ $b = Workflow::newCancellationScope(function () use ($simple, $input) {
+ return yield $simple->lower($input);
+ });
+
+ [$ra, $rb] = yield Promise::all([$a, $b]);
+
+ return sprintf('%s|%s|%s', $ra, $input, $rb);
+ }
+}
diff --git a/tests/temporal/Workflow/PeriodicWorkflow.php b/tests/temporal/Workflow/PeriodicWorkflow.php
new file mode 100644
index 00000000..08f5f2fa
--- /dev/null
+++ b/tests/temporal/Workflow/PeriodicWorkflow.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class PeriodicWorkflow
+{
+ #[WorkflowMethod(name: 'PeriodicWorkflow')]
+ public function handler()
+ {
+ error_log("GOT SOMETHING" . print_r(Workflow::getLastCompletionResult(), true));
+
+ // todo: get last completion result
+ return 'OK';
+ }
+}
diff --git a/tests/temporal/Workflow/ProtoPayloadWorkflow.php b/tests/temporal/Workflow/ProtoPayloadWorkflow.php
new file mode 100644
index 00000000..7adbed1e
--- /dev/null
+++ b/tests/temporal/Workflow/ProtoPayloadWorkflow.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Api\Common\V1\WorkflowExecution;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class ProtoPayloadWorkflow
+{
+ #[WorkflowMethod(name: 'ProtoPayloadWorkflow')]
+ public function handler(): iterable
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $e = new WorkflowExecution();
+ $e->setWorkflowId('workflow id');
+ $e->setRunId('run id');
+
+ /** @var WorkflowExecution $e2 */
+ $e2 = yield $simple->updateRunID($e);
+ assert($e2->getWorkflowId() === $e->getWorkflowId());
+ assert($e2->getRunId() === 'updated');
+
+ return $e2;
+ }
+}
diff --git a/tests/temporal/Workflow/QueryWorkflow.php b/tests/temporal/Workflow/QueryWorkflow.php
new file mode 100644
index 00000000..96e41582
--- /dev/null
+++ b/tests/temporal/Workflow/QueryWorkflow.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * This file is part of Temporal package.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class QueryWorkflow
+{
+ private int $counter = 0;
+
+ #[Workflow\SignalMethod(name: "add")]
+ public function add(
+ int $value
+ ) {
+ $this->counter += $value;
+ }
+
+ #[Workflow\QueryMethod(name: "get")]
+ public function get(): int
+ {
+ return $this->counter;
+ }
+
+ #[WorkflowMethod]
+ public function handler()
+ {
+ // collect signals during one second
+ yield Workflow::timer(1);
+
+ return $this->counter;
+ }
+}
diff --git a/tests/temporal/Workflow/RuntimeSignalWorkflow.php b/tests/temporal/Workflow/RuntimeSignalWorkflow.php
new file mode 100644
index 00000000..f700af72
--- /dev/null
+++ b/tests/temporal/Workflow/RuntimeSignalWorkflow.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use React\Promise\Deferred;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class RuntimeSignalWorkflow
+{
+ #[WorkflowMethod]
+ public function handler()
+ {
+ $wait1 = new Deferred();
+ $wait2 = new Deferred();
+
+ $counter = 0;
+
+ Workflow::registerSignal('add', function ($value) use (&$counter, $wait1, $wait2) {
+ $counter += $value;
+ $wait1->resolve($value);
+ $wait2->resolve($value);
+ });
+
+ yield $wait1;
+ yield $wait2;
+
+ return $counter;
+ }
+}
diff --git a/tests/temporal/Workflow/SagaWorkflow.php b/tests/temporal/Workflow/SagaWorkflow.php
new file mode 100644
index 00000000..e47c0203
--- /dev/null
+++ b/tests/temporal/Workflow/SagaWorkflow.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * This file is part of Temporal package.
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Common\RetryOptions;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Workflow;
+
+#[Workflow\WorkflowInterface]
+class SagaWorkflow
+{
+ #[Workflow\WorkflowMethod(name: 'SagaWorkflow')]
+ public function run()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(60)
+ ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(1))
+ );
+
+ $saga = new Workflow\Saga();
+ $saga->setParallelCompensation(true);
+
+ try {
+ yield $simple->echo('test');
+ $saga->addCompensation(
+ function () use ($simple) {
+ yield $simple->echo('compensate echo');
+ }
+ );
+
+ yield $simple->lower('TEST');
+ $saga->addCompensation(
+ function () use ($simple) {
+ yield $simple->lower('COMPENSATE LOWER');
+ }
+ );
+
+ yield $simple->fail();
+ } catch (\Throwable $e) {
+ yield $saga->compensate();
+ throw $e;
+ }
+ }
+}
diff --git a/tests/temporal/Workflow/SideEffectWorkflow.php b/tests/temporal/Workflow/SideEffectWorkflow.php
new file mode 100644
index 00000000..95d396e4
--- /dev/null
+++ b/tests/temporal/Workflow/SideEffectWorkflow.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Common\Uuid;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class SideEffectWorkflow
+{
+ #[WorkflowMethod(name: 'SideEffectWorkflow')]
+ public function handler(string $input): iterable
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $result = yield Workflow::sideEffect(
+ function () use ($input) {
+ return $input . '-42';
+ }
+ );
+
+ return yield $simple->lower($result);
+ }
+}
diff --git a/tests/temporal/Workflow/SignalChildViaStubWorkflow.php b/tests/temporal/Workflow/SignalChildViaStubWorkflow.php
new file mode 100644
index 00000000..828086fc
--- /dev/null
+++ b/tests/temporal/Workflow/SignalChildViaStubWorkflow.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class SignalChildViaStubWorkflow
+{
+ #[WorkflowMethod(name: 'SignalChildViaStubWorkflow')]
+ public function handler()
+ {
+ // typed stub
+ $simple = Workflow::newChildWorkflowStub(SimpleSignalledWorkflow::class);
+
+ // start execution
+ $call = $simple->handler();
+
+ yield $simple->add(8);
+
+ // expects 8
+ return yield $call;
+ }
+}
diff --git a/tests/temporal/Workflow/SimpleDTOWorkflow.php b/tests/temporal/Workflow/SimpleDTOWorkflow.php
new file mode 100644
index 00000000..bd39a0a0
--- /dev/null
+++ b/tests/temporal/Workflow/SimpleDTOWorkflow.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+use Temporal\Tests\DTO\Message;
+use Temporal\Tests\DTO\User;
+
+#[Workflow\WorkflowInterface]
+class SimpleDTOWorkflow
+{
+ #[WorkflowMethod(name: 'SimpleDTOWorkflow')]//, returnType: Message::class)]
+ public function handler(
+ User $user
+ ) {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(5)
+ );
+
+ $value = yield $simple->greet($user);
+
+ if (!$value instanceof Message) {
+ return "FAIL";
+ }
+
+ return $value;
+ }
+}
diff --git a/tests/temporal/Workflow/SimpleHeartbeatWorkflow.php b/tests/temporal/Workflow/SimpleHeartbeatWorkflow.php
new file mode 100644
index 00000000..c9999cd1
--- /dev/null
+++ b/tests/temporal/Workflow/SimpleHeartbeatWorkflow.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\HeartBeatActivity;
+
+#[Workflow\WorkflowInterface]
+class SimpleHeartbeatWorkflow
+{
+ #[WorkflowMethod(name: 'SimpleHeartbeatWorkflow')]
+ public function handler(int $iterations): iterable
+ {
+ $act = Workflow::newActivityStub(
+ HeartBeatActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(50)
+ );
+
+ return yield $act->doSomething($iterations);
+ }
+}
diff --git a/tests/temporal/Workflow/SimpleSignalledWorkflow.php b/tests/temporal/Workflow/SimpleSignalledWorkflow.php
new file mode 100644
index 00000000..0df25a65
--- /dev/null
+++ b/tests/temporal/Workflow/SimpleSignalledWorkflow.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class SimpleSignalledWorkflow
+{
+ private $counter = 0;
+
+ #[Workflow\SignalMethod(name: "add")]
+ public function add(
+ int $value
+ ) {
+ $this->counter += $value;
+ }
+
+ #[WorkflowMethod(name: 'SimpleSignalledWorkflow')]
+ public function handler(): iterable
+ {
+ // collect signals during one second
+ yield Workflow::timer(1);
+
+ return $this->counter;
+ }
+}
diff --git a/tests/temporal/Workflow/SimpleSignalledWorkflowWithSleep.php b/tests/temporal/Workflow/SimpleSignalledWorkflowWithSleep.php
new file mode 100644
index 00000000..d10ba04a
--- /dev/null
+++ b/tests/temporal/Workflow/SimpleSignalledWorkflowWithSleep.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class SimpleSignalledWorkflowWithSleep
+{
+ private $counter = 0;
+
+ #[Workflow\SignalMethod(name: "add")]
+ public function add(
+ int $value
+ ) {
+ $this->counter += $value;
+ }
+
+ #[WorkflowMethod(name: 'SimpleSignalledWorkflowWithSleep')]
+ public function handler(): iterable
+ {
+ // collect signals during one second
+ yield Workflow::timer(1);
+
+ if (!Workflow::isReplaying()) {
+ sleep(1);
+ }
+
+ return $this->counter;
+ }
+}
diff --git a/tests/temporal/Workflow/SimpleWorkflow.php b/tests/temporal/Workflow/SimpleWorkflow.php
new file mode 100644
index 00000000..36e12f69
--- /dev/null
+++ b/tests/temporal/Workflow/SimpleWorkflow.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Common\RetryOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class SimpleWorkflow
+{
+ #[WorkflowMethod(name: 'SimpleWorkflow')]
+ public function handler(
+ string $input
+ ): iterable {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()
+ ->withStartToCloseTimeout(5)
+ ->withRetryOptions(
+ RetryOptions::new()->withMaximumAttempts(2)
+ )
+ );
+
+ return yield $simple->echo($input);
+ }
+}
diff --git a/tests/temporal/Workflow/TimerWorkflow.php b/tests/temporal/Workflow/TimerWorkflow.php
new file mode 100644
index 00000000..ab60d6c9
--- /dev/null
+++ b/tests/temporal/Workflow/TimerWorkflow.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class TimerWorkflow
+{
+ #[WorkflowMethod(name: 'TimerWorkflow')]
+ public function handler(string $input): iterable
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ yield Workflow::timer(1);
+
+ return yield $simple->lower($input);
+ }
+}
diff --git a/tests/temporal/Workflow/WaitWorkflow.php b/tests/temporal/Workflow/WaitWorkflow.php
new file mode 100644
index 00000000..826952c1
--- /dev/null
+++ b/tests/temporal/Workflow/WaitWorkflow.php
@@ -0,0 +1,33 @@
+<?php
+
+
+namespace Temporal\Tests\Workflow;
+
+
+use Temporal\Workflow;
+use Temporal\Workflow\SignalMethod;
+use Temporal\Workflow\WorkflowInterface;
+use Temporal\Workflow\WorkflowMethod;
+
+#[WorkflowInterface]
+class WaitWorkflow
+{
+ private bool $ready = false;
+ private string $value;
+
+ #[SignalMethod]
+ public function unlock(
+ string $value
+ ) {
+ $this->ready = true;
+ $this->value = $value;
+ }
+
+ #[WorkflowMethod(name: 'WaitWorkflow')]
+ public function run()
+ {
+ yield Workflow::await(fn() => $this->ready);
+
+ return $this->value;
+ }
+}
diff --git a/tests/temporal/Workflow/WithChildStubWorkflow.php b/tests/temporal/Workflow/WithChildStubWorkflow.php
new file mode 100644
index 00000000..cdebe3d8
--- /dev/null
+++ b/tests/temporal/Workflow/WithChildStubWorkflow.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class WithChildStubWorkflow
+{
+ #[WorkflowMethod(name: 'WithChildStubWorkflow')]
+ public function handler(string $input): iterable
+ {
+ $child = Workflow::newChildWorkflowStub(SimpleWorkflow::class);
+
+ return 'Child: ' . (yield $child->handler('child ' . $input));
+ }
+}
diff --git a/tests/temporal/Workflow/WithChildWorkflow.php b/tests/temporal/Workflow/WithChildWorkflow.php
new file mode 100644
index 00000000..aac0979b
--- /dev/null
+++ b/tests/temporal/Workflow/WithChildWorkflow.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+
+#[Workflow\WorkflowInterface]
+class WithChildWorkflow
+{
+ #[WorkflowMethod(name: 'WithChildWorkflow')]
+ public function handler(
+ string $input
+ ): iterable {
+ $result = yield Workflow::executeChildWorkflow(
+ 'SimpleWorkflow',
+ ['child ' . $input],
+ Workflow\ChildWorkflowOptions::new()
+ );
+
+ return 'Child: ' . $result;
+ }
+}
diff --git a/tests/temporal/Workflow/WorkflowWithSequence.php b/tests/temporal/Workflow/WorkflowWithSequence.php
new file mode 100644
index 00000000..9e813a9c
--- /dev/null
+++ b/tests/temporal/Workflow/WorkflowWithSequence.php
@@ -0,0 +1,30 @@
+<?php
+
+
+namespace Temporal\Tests\Workflow;
+
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class WorkflowWithSequence
+{
+ #[WorkflowMethod(name: 'WorkflowWithSequence')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $a = $simple->echo('a');
+ $b = $simple->echo('b');
+
+ yield $a;
+ yield $b;
+
+ return 'OK';
+ }
+}
diff --git a/tests/temporal/Workflow/WorkflowWithSignalledSteps.php b/tests/temporal/Workflow/WorkflowWithSignalledSteps.php
new file mode 100644
index 00000000..5f1af766
--- /dev/null
+++ b/tests/temporal/Workflow/WorkflowWithSignalledSteps.php
@@ -0,0 +1,51 @@
+<?php
+
+
+namespace Temporal\Tests\Workflow;
+
+use React\Promise\Deferred;
+use React\Promise\PromiseInterface;
+use Temporal\Activity\ActivityOptions;
+use Temporal\Workflow;
+use Temporal\Workflow\WorkflowMethod;
+use Temporal\Tests\Activity\SimpleActivity;
+
+#[Workflow\WorkflowInterface]
+class WorkflowWithSignalledSteps
+{
+ #[WorkflowMethod(name: 'WorkflowWithSignalledSteps')]
+ public function handler()
+ {
+ $simple = Workflow::newActivityStub(
+ SimpleActivity::class,
+ ActivityOptions::new()->withStartToCloseTimeout(5)
+ );
+
+ $value = 0;
+ Workflow::registerQuery('value', function () use (&$value) {
+ return $value;
+ });
+
+ yield $this->promiseSignal('begin');
+ $value++;
+
+ yield $this->promiseSignal('next1');
+ $value++;
+
+ yield $this->promiseSignal('next2');
+ $value++;
+
+ return $value;
+ }
+
+ // is this correct?
+ private function promiseSignal(string $name): PromiseInterface
+ {
+ $signal = new Deferred();
+ Workflow::registerSignal($name, function ($value) use ($signal) {
+ $signal->resolve($value);
+ });
+
+ return $signal->promise();
+ }
+}