From 20fc7bb5da2eaced7de1507e9ccafe76ea937ba9 Mon Sep 17 00:00:00 2001 From: Dmitriy Derepko Date: Tue, 23 Dec 2025 12:47:08 +0400 Subject: [PATCH] chore: enhanced docs, applied code style adjustments --- resources/client.meta-storm.xml | 17 ++++ tests/.meta-storm.xml | 7 ++ .../Acceptance/App/Attribute/RetryOptions.php | 15 ++++ tests/Acceptance/App/Attribute/Stub.php | 15 ++++ .../Acceptance/Harness/Activity/BasicTest.php | 33 ++++---- .../Harness/Activity/CancelTryCancelTest.php | 77 +++++++++---------- .../Harness/Activity/RetryOnErrorTest.php | 43 ++++++----- .../ContinueAsNew/ContinueAsSameTest.php | 28 ++++--- .../DataConverter/BinaryProtobufTest.php | 39 +++++----- .../Harness/DataConverter/BinaryTest.php | 67 ++++++++-------- .../Harness/DataConverter/CodecTest.php | 46 ++++++----- .../DataConverter/JsonProtobufTest.php | 26 ++++--- .../Harness/DataConverter/JsonTest.php | 19 +++-- .../{EmptyTest.php => NullTest.php} | 17 +++- .../EagerWorkflow/SuccessfulStartTest.php | 26 +++---- .../Harness/Query/SuccessfulQueryTest.php | 6 +- .../Harness/Query/UnexpectedArgumentsTest.php | 7 +- .../Query/UnexpectedQueryTypeNameTest.php | 3 +- .../Query/UnexpectedReturnTypeTest.php | 3 +- .../Harness/Schedule/BackfillTest.php | 6 +- 20 files changed, 292 insertions(+), 208 deletions(-) create mode 100644 tests/.meta-storm.xml rename tests/Acceptance/Harness/DataConverter/{EmptyTest.php => NullTest.php} (81%) diff --git a/resources/client.meta-storm.xml b/resources/client.meta-storm.xml index a3a54236a..b51398e0c 100644 --- a/resources/client.meta-storm.xml +++ b/resources/client.meta-storm.xml @@ -29,7 +29,24 @@ argument="0" > + + + + + + + + + diff --git a/tests/.meta-storm.xml b/tests/.meta-storm.xml new file mode 100644 index 000000000..b1000d2e3 --- /dev/null +++ b/tests/.meta-storm.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/Acceptance/App/Attribute/RetryOptions.php b/tests/Acceptance/App/Attribute/RetryOptions.php index ea5ace526..a13f01450 100644 --- a/tests/Acceptance/App/Attribute/RetryOptions.php +++ b/tests/Acceptance/App/Attribute/RetryOptions.php @@ -25,10 +25,25 @@ class RetryOptions * @param int<0, max> $maximumAttempts */ public function __construct( + /** + * @see CommonOptions::withInitialInterval() + */ public ?string $initialInterval = CommonOptions::DEFAULT_INITIAL_INTERVAL, + /** + * @see CommonOptions::withBackoffCoefficient() + */ public float $backoffCoefficient = CommonOptions::DEFAULT_BACKOFF_COEFFICIENT, + /** + * @see CommonOptions::withMaximumInterval() + */ public ?string $maximumInterval = CommonOptions::DEFAULT_MAXIMUM_INTERVAL, + /** + * @see CommonOptions::withMaximumAttempts() + */ public int $maximumAttempts = CommonOptions::DEFAULT_MAXIMUM_ATTEMPTS, + /** + * @see CommonOptions::withNonRetryableExceptions() + */ public array $nonRetryableExceptions = CommonOptions::DEFAULT_NON_RETRYABLE_EXCEPTIONS, ) {} diff --git a/tests/Acceptance/App/Attribute/Stub.php b/tests/Acceptance/App/Attribute/Stub.php index 1fc2aa295..602467f58 100644 --- a/tests/Acceptance/App/Attribute/Stub.php +++ b/tests/Acceptance/App/Attribute/Stub.php @@ -23,11 +23,26 @@ final class Stub */ public function __construct( public string $type, + /** + * @see WorkflowOptions::withEagerStart() + */ public bool $eagerStart = false, + /** + * @see WorkflowOptions::withWorkflowId() + */ public ?string $workflowId = null, + /** + * @see WorkflowOptions::withWorkflowExecutionTimeout() + */ public ?string $executionTimeout = null, public array $args = [], + /** + * @see WorkflowOptions::withMemo() + */ public array $memo = [], + /** + * @see WorkflowOptions::withRetryOptions() + */ ?RetryOptions $retryOptions = null, ) { $this->retryOptions = $retryOptions?->toRetryOptions(); diff --git a/tests/Acceptance/Harness/Activity/BasicTest.php b/tests/Acceptance/Harness/Activity/BasicTest.php index dfbd947a6..0bc166157 100644 --- a/tests/Acceptance/Harness/Activity/BasicTest.php +++ b/tests/Acceptance/Harness/Activity/BasicTest.php @@ -15,26 +15,25 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -/* - -# Basic activity - -The most basic workflow which just runs an activity and returns its result. -Importantly, without setting a workflow execution timeout. - -# Detailed spec - -It's important that the workflow execution timeout is not set here, because server will propagate that to all un-set -activity timeouts. We had a bug where TS would crash (after proto changes from gogo to google) because it was expecting -timeouts to be set to zero rather than null. - -*/ - +/** + * # Basic activity + * + * The most basic workflow which just runs an activity and returns its result. + * Importantly, without setting a workflow execution timeout. + * + * # Detailed spec + * + * It's important that the workflow execution timeout is not set here, because server will propagate that to all un-set + * activity timeouts. We had a bug where TS would crash (after proto changes from gogo to google) because it was expecting + * timeouts to be set to zero rather than null. + */ class BasicTest extends TestCase { #[Test] - public static function check(#[Stub('Harness_Activity_Basic')]WorkflowStubInterface $stub): void - { + public static function check( + #[Stub('Harness_Activity_Basic')] + WorkflowStubInterface $stub, + ): void { self::assertSame('echo', $stub->getResult()); } } diff --git a/tests/Acceptance/Harness/Activity/CancelTryCancelTest.php b/tests/Acceptance/Harness/Activity/CancelTryCancelTest.php index 1f729401d..853aa9a1c 100644 --- a/tests/Acceptance/Harness/Activity/CancelTryCancelTest.php +++ b/tests/Acceptance/Harness/Activity/CancelTryCancelTest.php @@ -5,7 +5,6 @@ namespace Temporal\Tests\Acceptance\Harness\Activity\CancelTryCancel; use PHPUnit\Framework\Attributes\Test; -use React\Promise\PromiseInterface; use Temporal\Activity; use Temporal\Activity\ActivityInterface; use Temporal\Activity\ActivityMethod; @@ -21,41 +20,41 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -/* - -# Activity cancellation - Try Cancel mode -Activities may be cancelled in three different ways, this feature spec covers the -Try Cancel mode. - -Each feature workflow in this folder should start an activity and cancel it -using the Try Cancel mode. The implementation should demonstrate that the activity -keeps receives a cancel request after the workflow has issued it, but the workflow -immediately should proceed with the activity result being cancelled. - -## Detailed spec - -* When the SDK issues the activity cancel request command, server will write an - activity cancel requested event to history -* The workflow immediately resolves the activity with its result being cancelled -* Server will notify the activity cancellation has been requested via a response - to activity heartbeating -* The activity may ignore the cancellation request if it explicitly chooses to - -## Feature implementation - -* Execute activity that heartbeats and checks cancellation - * If a minute passes without cancellation, send workflow a signal that it timed out - * If cancellation is received, send workflow a signal that it was cancelled -* Cancel activity and confirm cancellation error is returned -* Check in the workflow that the signal sent from the activity is showing it was cancelled - -*/ - +/** + * # Activity cancellation - Try Cancel mode + * + * Activities may be cancelled in three different ways, this feature spec covers the + * Try Cancel mode. + * + * Each feature workflow in this folder should start an activity and cancel it + * using the Try Cancel mode. The implementation should demonstrate that the activity + * keeps receives a cancel request after the workflow has issued it, but the workflow + * immediately should proceed with the activity result being cancelled. + * + * ## Detailed spec + * + * When the SDK issues the activity cancel request command, server will write an + * activity cancel requested event to history + * The workflow immediately resolves the activity with its result being cancelled + * Server will notify the activity cancellation has been requested via a response + * to activity heartbeating + * The activity may ignore the cancellation request if it explicitly chooses to + * + * ## Feature implementation + * + * Execute activity that heartbeats and checks cancellation + * If a minute passes without cancellation, send workflow a signal that it timed out + * If cancellation is received, send workflow a signal that it was cancelled + * Cancel activity and confirm cancellation error is returned + * Check in the workflow that the signal sent from the activity is showing it was cancelled + */ class CancelTryCancelTest extends TestCase { #[Test] - public static function check(#[Stub('Harness_Activity_CancelTryCancel')]WorkflowStubInterface $stub): void - { + public static function check( + #[Stub('Harness_Activity_CancelTryCancel')] + WorkflowStubInterface $stub, + ): void { self::assertSame('cancelled', $stub->getResult(timeout: 10)); } } @@ -76,7 +75,7 @@ public function run() ->withHeartbeatTimeout('5 seconds') # Disable retry ->withRetryOptions(RetryOptions::new()->withMaximumAttempts(1)) - ->withCancellationType(Activity\ActivityCancellationType::TryCancel) + ->withCancellationType(Activity\ActivityCancellationType::TryCancel), ); $scope = Workflow::async(static fn() => $activity->cancellableActivity()); @@ -98,7 +97,7 @@ public function run() } #[Workflow\SignalMethod('activity_result')] - public function activityResult(string $result) + public function activityResult(string $result): void { $this->result = $result; } @@ -109,14 +108,10 @@ class FeatureActivity { public function __construct( private readonly WorkflowClientInterface $client, - ) { - } + ) {} - /** - * @return PromiseInterface - */ #[ActivityMethod('cancellable_activity')] - public function cancellableActivity() + public function cancellableActivity(): void { # Heartbeat every second for a minute $result = 'timeout'; diff --git a/tests/Acceptance/Harness/Activity/RetryOnErrorTest.php b/tests/Acceptance/Harness/Activity/RetryOnErrorTest.php index 9c6f514b5..574649007 100644 --- a/tests/Acceptance/Harness/Activity/RetryOnErrorTest.php +++ b/tests/Acceptance/Harness/Activity/RetryOnErrorTest.php @@ -20,37 +20,38 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -/* - -# Retrying activities on error - -Failed activities can retry in a number of ways. This is configurable by retry policies that govern if and -how a failed activity may retry. - -## Feature implementation - -* Workflow executes activity with 5 max attempts and low backoff -* Activity errors every time with the attempt that failed -* Workflow waits on activity and re-bubbles its same error -* Confirm the right attempt error message is present - -*/ - +/** + * # Retrying activities on error + * + * Failed activities can retry in a number of ways. This is configurable by retry policies that govern if and + * how a failed activity may retry. + * + * ## Feature implementation + * + * Workflow executes activity with 5 max attempts and low backoff + * Activity errors every time with the attempt that failed + * Workflow waits on activity and re-bubbles its same error + * Confirm the right attempt error message is present + */ class RetryOnErrorTest extends TestCase { #[Test] - public static function check(#[Stub('Harness_Activity_CancelTryCancel')]WorkflowStubInterface $stub): void - { + public static function check( + #[Stub('Harness_Activity_CancelTryCancel')] + WorkflowStubInterface $stub, + ): void { try { $stub->getResult(); - throw new \Exception('Expected WorkflowFailedException'); } catch (WorkflowFailedException $e) { self::assertInstanceOf(ActivityFailure::class, $e->getPrevious()); /** @var ActivityFailure $failure */ $failure = $e->getPrevious()->getPrevious(); self::assertInstanceOf(ApplicationFailure::class, $failure); self::assertStringContainsStringIgnoringCase('activity attempt 5 failed', $failure->getOriginalMessage()); + return; } + + throw new \Exception('Expected getResult() produced WorkflowFailedException'); } } @@ -58,7 +59,7 @@ public static function check(#[Stub('Harness_Activity_CancelTryCancel')]Workflow class FeatureWorkflow { #[WorkflowMethod('Harness_Activity_CancelTryCancel')] - public function run() + public function run(): iterable { # Allow 4 retries with basically no backoff yield Workflow::newActivityStub( @@ -71,7 +72,7 @@ public function run() # Do not increase retry backoff each time ->withBackoffCoefficient(1) # 5 total maximum attempts - ->withMaximumAttempts(5) + ->withMaximumAttempts(5), ), )->alwaysFailActivity(); } diff --git a/tests/Acceptance/Harness/ContinueAsNew/ContinueAsSameTest.php b/tests/Acceptance/Harness/ContinueAsNew/ContinueAsSameTest.php index f9b8e60da..2c2acc2be 100644 --- a/tests/Acceptance/Harness/ContinueAsNew/ContinueAsSameTest.php +++ b/tests/Acceptance/Harness/ContinueAsNew/ContinueAsSameTest.php @@ -12,29 +12,33 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -\define('INPUT_DATA', 'InputData'); -\define('MEMO_KEY', 'MemoKey'); -\define('MEMO_VALUE', 'MemoValue'); -\define('WORKFLOW_ID', 'TestID'); - +/** + * # Continues workflow execution + */ class ContinueAsSameTest extends TestCase { + private const INPUT_DATA = 'InputData'; + private const MEMO_KEY = 'MemoKey'; + private const MEMO_VALUE = 'MemoValue'; + private const WORKFLOW_ID = 'TestID'; + #[Test] public static function check( #[Stub( type: 'Harness_ContinueAsNew_ContinueAsSame', - workflowId: WORKFLOW_ID, - args: [INPUT_DATA], - memo: [MEMO_KEY => MEMO_VALUE], + workflowId: self::WORKFLOW_ID, + args: [self::INPUT_DATA], + memo: [self::MEMO_KEY => self::MEMO_VALUE], )] WorkflowStubInterface $stub, ): void { - self::assertSame(INPUT_DATA, $stub->getResult()); + self::assertSame(self::INPUT_DATA, $stub->getResult()); # Workflow ID does not change after continue as new - self::assertSame(WORKFLOW_ID, $stub->getExecution()->getID()); + self::assertSame(self::WORKFLOW_ID, $stub->getExecution()->getID()); # Memos do not change after continue as new $description = $stub->describe(); - self::assertSame([MEMO_KEY => MEMO_VALUE], $description->info->memo->getValues()); + self::assertSame(5, $description->info->historyLength); + self::assertSame([self::MEMO_KEY => self::MEMO_VALUE], $description->info->memo->getValues()); } } @@ -42,7 +46,7 @@ public static function check( class FeatureWorkflow { #[WorkflowMethod('Harness_ContinueAsNew_ContinueAsSame')] - public function run(string $input) + public function run(string $input): iterable { if (!empty(Workflow::getInfo()->continuedExecutionRunId)) { return $input; diff --git a/tests/Acceptance/Harness/DataConverter/BinaryProtobufTest.php b/tests/Acceptance/Harness/DataConverter/BinaryProtobufTest.php index a186088e7..fcea23ea4 100644 --- a/tests/Acceptance/Harness/DataConverter/BinaryProtobufTest.php +++ b/tests/Acceptance/Harness/DataConverter/BinaryProtobufTest.php @@ -20,41 +20,33 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -const EXPECTED_RESULT = 0xDEADBEEF; -\define(__NAMESPACE__ . '\INPUT', (new DataBlob())->setData(EXPECTED_RESULT)); - +/** + * # Binary Protobuf Payload Encoding + * + * Test that binary protobuf payload encoding works well. + */ class BinaryProtobufTest extends TestCase { - private GrpcCallInterceptor $interceptor; + private const EXPECTED_RESULT = 0xDEADBEEF; - protected function setUp(): void - { - $this->interceptor = new GrpcCallInterceptor(); - parent::setUp(); - } - - public function pipelineProvider(): PipelineProvider - { - return new SimplePipelineProvider([$this->interceptor]); - } + private GrpcCallInterceptor $interceptor; #[Test] public function check( - #[Stub('Harness_DataConverter_BinaryProtobuf', args: [INPUT])] + #[Stub('Harness_DataConverter_BinaryProtobuf', args: [new DataBlob(['data' => self::EXPECTED_RESULT])])] #[Client( pipelineProvider: [self::class, 'pipelineProvider'], payloadConverters: [ProtoConverter::class], )] WorkflowStubInterface $stub, ): void { - /** @var DataBlob $result */ $result = $stub->getResult(DataBlob::class); # Check that binary protobuf message was decoded in the Workflow and sent back. # But we don't check the result Payload encoding, because we can't configure different Payload encoders # on the server side for different Harness features. # There `json/protobuf` converter is used for protobuf messages by default on the server side. - self::assertEquals(EXPECTED_RESULT, $result->getData()); + self::assertEquals(self::EXPECTED_RESULT, $result->getData()); # Check arguments self::assertNotNull($this->interceptor->startRequest); @@ -65,13 +57,24 @@ public function check( self::assertSame('binary/protobuf', $payload->getMetadata()['encoding']); self::assertSame('temporal.api.common.v1.DataBlob', $payload->getMetadata()['messageType']); } + + public function pipelineProvider(): PipelineProvider + { + return new SimplePipelineProvider([$this->interceptor]); + } + + protected function setUp(): void + { + $this->interceptor = new GrpcCallInterceptor(); + parent::setUp(); + } } #[WorkflowInterface] class FeatureWorkflow { #[WorkflowMethod('Harness_DataConverter_BinaryProtobuf')] - public function run(DataBlob $data) + public function run(DataBlob $data): DataBlob { return $data; } diff --git a/tests/Acceptance/Harness/DataConverter/BinaryTest.php b/tests/Acceptance/Harness/DataConverter/BinaryTest.php index 2324e57bc..974f32035 100644 --- a/tests/Acceptance/Harness/DataConverter/BinaryTest.php +++ b/tests/Acceptance/Harness/DataConverter/BinaryTest.php @@ -23,39 +23,32 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -/* -# Binary payload converter - -Binary values can be converted to and from `binary/plain` Payloads. - -Steps: - -- run a echo workflow that accepts and returns binary value `0xdeadbeef` -- verify client result is binary `0xdeadbeef` -- get result payload of WorkflowExecutionCompleted event from workflow history -- load JSON payload from `./payload.json` and compare it to result payload -- get argument payload of WorkflowExecutionStarted event from workflow history -- verify that argument and result payloads are the same - - -# Detailed spec - -`metadata.encoding = toBinary("binary/plain")` -*/ - -const CODEC_ENCODING = 'binary/plain'; -\define(__NAMESPACE__ . '\EXPECTED_RESULT', (string)0xDEADBEEF); -\define(__NAMESPACE__ . '\INPUT', new Bytes(EXPECTED_RESULT)); +/** + * # Binary payload converter + * + * Binary values can be converted to and from `binary/plain` Payloads. + * + * Steps: + * + * - run a echo workflow that accepts and returns binary value `0xdeadbeef` + * - verify client result is binary `0xdeadbeef` + * - get result payload of WorkflowExecutionCompleted event from workflow history + * - load JSON payload from `./payload.json` and compare it to result payload + * - get argument payload of WorkflowExecutionStarted event from workflow history + * - verify that argument and result payloads are the same + * + * + * # Detailed spec + * + * `metadata.encoding = toBinary("binary/plain")` + */ class BinaryTest extends TestCase { - private Interceptor $interceptor; + private const EXPECTED_RESULT = "" . 0xDEADBEEF; + private const CODEC_ENCODING = 'binary/plain'; - protected function setUp(): void - { - $this->interceptor = new Interceptor(); - parent::setUp(); - } + private Interceptor $interceptor; public function pipelineProvider(): PipelineProvider { @@ -64,14 +57,14 @@ public function pipelineProvider(): PipelineProvider #[Test] public function check( - #[Stub('Harness_DataConverter_Binary', args: [INPUT])] + #[Stub('Harness_DataConverter_Binary', args: [new Bytes(self::EXPECTED_RESULT)])] #[Client(pipelineProvider: [self::class, 'pipelineProvider'])] WorkflowStubInterface $stub, ): void { /** @var Bytes $result */ $result = $stub->getResult(Bytes::class); - self::assertEquals(EXPECTED_RESULT, $result->getData()); + self::assertEquals(self::EXPECTED_RESULT, $result->getData()); # Check arguments self::assertNotNull($this->interceptor->startRequest); @@ -81,12 +74,18 @@ public function check( $payload = $this->interceptor->startRequest->getInput()?->getPayloads()[0] ?? null; self::assertNotNull($payload); - self::assertSame(CODEC_ENCODING, $payload->getMetadata()['encoding']); + self::assertSame(self::CODEC_ENCODING, $payload->getMetadata()['encoding']); // Check result value from interceptor /** @var Payload $resultPayload */ $resultPayload = $this->interceptor->result->toPayloads()->getPayloads()[0]; - self::assertSame(CODEC_ENCODING, $resultPayload->getMetadata()['encoding']); + self::assertSame(self::CODEC_ENCODING, $resultPayload->getMetadata()['encoding']); + } + + protected function setUp(): void + { + $this->interceptor = new Interceptor(); + parent::setUp(); } } @@ -94,7 +93,7 @@ public function check( class FeatureWorkflow { #[WorkflowMethod('Harness_DataConverter_Binary')] - public function run(Bytes $data) + public function run(Bytes $data): Bytes { return $data; } diff --git a/tests/Acceptance/Harness/DataConverter/CodecTest.php b/tests/Acceptance/Harness/DataConverter/CodecTest.php index a934c00c3..1ef30c76d 100644 --- a/tests/Acceptance/Harness/DataConverter/CodecTest.php +++ b/tests/Acceptance/Harness/DataConverter/CodecTest.php @@ -23,19 +23,17 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -const CODEC_ENCODING = 'my-encoding'; const EXPECTED_RESULT = new DTO(spec: true); +/** + * # Codec Payload Encoding + * + * Test custom codec payload encoding. + */ class CodecTest extends TestCase { private ResultInterceptor $interceptor; - protected function setUp(): void - { - $this->interceptor = new ResultInterceptor(); - parent::setUp(); - } - public function pipelineProvider(): PipelineProvider { return new SimplePipelineProvider([$this->interceptor]); @@ -46,30 +44,36 @@ public function check( #[Stub('Harness_DataConverter_Codec', args: [EXPECTED_RESULT])] #[Client( pipelineProvider: [self::class, 'pipelineProvider'], - payloadConverters: [Base64PayloadCodec::class]), - ] + payloadConverters: [Base64PayloadCodec::class], + )] WorkflowStubInterface $stub, ): void { $result = $stub->getResult(); self::assertEquals(EXPECTED_RESULT, $result); - $result = $this->interceptor->result; + // Check arguments from interceptor $input = $this->interceptor->start; - self::assertNotNull($result); self::assertNotNull($input); + /** @var Payload $inputPayload */ + $inputPayload = $input->toPayloads()->getPayloads()[0]; + self::assertSame(Base64PayloadCodec::CODEC_ENCODING, $inputPayload->getMetadata()['encoding']); + self::assertSame(\base64_encode('{"spec":true}'), $inputPayload->getData()); // Check result value from interceptor + $result = $this->interceptor->result; + self::assertNotNull($result); /** @var Payload $resultPayload */ $resultPayload = $result->toPayloads()->getPayloads()[0]; - self::assertSame(CODEC_ENCODING, $resultPayload->getMetadata()['encoding']); + self::assertSame(Base64PayloadCodec::CODEC_ENCODING, $resultPayload->getMetadata()['encoding']); self::assertSame(\base64_encode('{"spec":true}'), $resultPayload->getData()); - // Check arguments from interceptor - /** @var Payload $inputPayload */ - $inputPayload = $input->toPayloads()->getPayloads()[0]; - self::assertSame(CODEC_ENCODING, $inputPayload->getMetadata()['encoding']); - self::assertSame(\base64_encode('{"spec":true}'), $inputPayload->getData()); + } + + protected function setUp(): void + { + $this->interceptor = new ResultInterceptor(); + parent::setUp(); } } @@ -89,8 +93,10 @@ public function run(mixed $data) class ResultInterceptor implements WorkflowClientCallsInterceptor { use WorkflowClientCallsInterceptorTrait; + public ?EncodedValues $result = null; public ?EncodedValues $start = null; + public function getResult(GetResultInput $input, callable $next): ?EncodedValues { return $this->result = $next($input); @@ -116,9 +122,11 @@ public function __construct(...$args) class Base64PayloadCodec implements PayloadConverterInterface { + public const CODEC_ENCODING = 'my-encoding'; + public function getEncodingType(): string { - return CODEC_ENCODING; + return self::CODEC_ENCODING; } public function toPayload($value): ?Payload @@ -126,7 +134,7 @@ public function toPayload($value): ?Payload return $value instanceof DTO ? (new Payload()) ->setData(\base64_encode(\json_encode($value, flags: \JSON_THROW_ON_ERROR))) - ->setMetadata(['encoding' => CODEC_ENCODING]) + ->setMetadata(['encoding' => self::CODEC_ENCODING]) : null; } diff --git a/tests/Acceptance/Harness/DataConverter/JsonProtobufTest.php b/tests/Acceptance/Harness/DataConverter/JsonProtobufTest.php index 781c03ca5..d1524cfb5 100644 --- a/tests/Acceptance/Harness/DataConverter/JsonProtobufTest.php +++ b/tests/Acceptance/Harness/DataConverter/JsonProtobufTest.php @@ -19,18 +19,16 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -const EXPECTED_RESULT = 0xDEADBEEF; -\define(__NAMESPACE__ . '\INPUT', (new DataBlob())->setData(EXPECTED_RESULT)); - +/** + * # JSON Protobuf Payload Encoding + * + * Test that JSON contains encoded protobuf payload which contains serialized `DataBlob` object. + */ class JsonProtobufTest extends TestCase { - private ResultInterceptor $interceptor; + private const EXPECTED_RESULT = 0xDEADBEEF; - protected function setUp(): void - { - $this->interceptor = new ResultInterceptor(); - parent::setUp(); - } + private ResultInterceptor $interceptor; public function pipelineProvider(): PipelineProvider { @@ -39,14 +37,14 @@ public function pipelineProvider(): PipelineProvider #[Test] public function check( - #[Stub('Harness_DataConverter_JsonProtobuf', args: [INPUT])] + #[Stub('Harness_DataConverter_JsonProtobuf', args: [new DataBlob(['data' => self::EXPECTED_RESULT])])] #[Client(pipelineProvider: [self::class, 'pipelineProvider'])] WorkflowStubInterface $stub, ): void { /** @var DataBlob $result */ $result = $stub->getResult(DataBlob::class); - self::assertEquals(EXPECTED_RESULT, $result->getData()); + self::assertEquals(self::EXPECTED_RESULT, $result->getData()); $result = $this->interceptor->result; self::assertNotNull($result); @@ -59,6 +57,12 @@ public function check( self::assertSame('temporal.api.common.v1.DataBlob', $payload->getMetadata()['messageType']); self::assertSame('{"data":"MzczNTkyODU1OQ=="}', $payload->getData()); } + + protected function setUp(): void + { + $this->interceptor = new ResultInterceptor(); + parent::setUp(); + } } #[WorkflowInterface] diff --git a/tests/Acceptance/Harness/DataConverter/JsonTest.php b/tests/Acceptance/Harness/DataConverter/JsonTest.php index 7ff4ea22c..37b078566 100644 --- a/tests/Acceptance/Harness/DataConverter/JsonTest.php +++ b/tests/Acceptance/Harness/DataConverter/JsonTest.php @@ -18,18 +18,17 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -\define(__NAMESPACE__ . '\EXPECTED_RESULT', (object)['spec' => true]); +\define(__NAMESPACE__ . '\EXPECTED_RESULT', (object) ['spec' => true]); +/** + * # JSON Payload Encoding + * + * Test that regular PHP structures like plain objects are encoded as JSON payloads. + */ class JsonTest extends TestCase { private ResultInterceptor $interceptor; - protected function setUp(): void - { - $this->interceptor = new ResultInterceptor(); - parent::setUp(); - } - public function pipelineProvider(): PipelineProvider { return new SimplePipelineProvider([$this->interceptor]); @@ -55,6 +54,12 @@ public function check( self::assertSame('json/plain', $payload->getMetadata()['encoding']); self::assertSame('{"spec":true}', $payload->getData()); } + + protected function setUp(): void + { + $this->interceptor = new ResultInterceptor(); + parent::setUp(); + } } #[WorkflowInterface] diff --git a/tests/Acceptance/Harness/DataConverter/EmptyTest.php b/tests/Acceptance/Harness/DataConverter/NullTest.php similarity index 81% rename from tests/Acceptance/Harness/DataConverter/EmptyTest.php rename to tests/Acceptance/Harness/DataConverter/NullTest.php index 904d988a2..1de5f1674 100644 --- a/tests/Acceptance/Harness/DataConverter/EmptyTest.php +++ b/tests/Acceptance/Harness/DataConverter/NullTest.php @@ -21,7 +21,13 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -class EmptyTest extends TestCase +/** + * # Null Payload Encoding + * + * Activities with `void` return type actually returns `null` as a result. + * Workflow check that acitivity's returning value is `null` and the history event contains `null` payload. + */ +class NullTest extends TestCase { #[Test] public function check( @@ -49,8 +55,11 @@ public function check( self::assertInstanceOf(Payload::class, $payload); \assert($payload instanceof Payload); - $decoded = \json_decode('{ "metadata": { "encoding": "YmluYXJ5L251bGw=" } }', true, 512, JSON_THROW_ON_ERROR); - self::assertEquals($decoded, \json_decode($payload->serializeToJsonString(), true, 512, JSON_THROW_ON_ERROR)); + self::assertEquals([ + 'metadata' => [ + 'encoding' => 'YmluYXJ5L251bGw=', // \base64_encode('binary/null'), + ], + ], \json_decode($payload->serializeToJsonString(), true, 512, JSON_THROW_ON_ERROR)); } } @@ -58,7 +67,7 @@ public function check( class FeatureWorkflow { #[WorkflowMethod('Harness_DataConverter_Empty')] - public function run() + public function run(): iterable { yield Workflow::newActivityStub( EmptyActivity::class, diff --git a/tests/Acceptance/Harness/EagerWorkflow/SuccessfulStartTest.php b/tests/Acceptance/Harness/EagerWorkflow/SuccessfulStartTest.php index 2b74b820b..0d74fb5e0 100644 --- a/tests/Acceptance/Harness/EagerWorkflow/SuccessfulStartTest.php +++ b/tests/Acceptance/Harness/EagerWorkflow/SuccessfulStartTest.php @@ -17,17 +17,9 @@ use Temporal\Workflow\WorkflowInterface; use Temporal\Workflow\WorkflowMethod; -\define('EXPECTED_RESULT', 'Hello World'); - class SuccessfulStartTest extends TestCase { - private grpcCallInterceptor $interceptor; - - protected function setUp(): void - { - $this->interceptor = new grpcCallInterceptor(); - parent::setUp(); - } + private CatchStartWorkflowExecutionResponseInterceptor $interceptor; public function pipelineProvider(): PipelineProvider { @@ -36,31 +28,37 @@ public function pipelineProvider(): PipelineProvider #[Test] public function start( - #[Stub('Harness_EagerWorkflow_SuccessfulStart', eagerStart: true,)] + #[Stub('Harness_EagerWorkflow_SuccessfulStart', eagerStart: true)] #[Client(timeout: 30, pipelineProvider: [self::class, 'pipelineProvider'])] WorkflowStubInterface $stub, ): void { // Check the result and the eager workflow proof - self::assertSame(EXPECTED_RESULT, $stub->getResult()); + self::assertSame('ok', $stub->getResult()); self::assertNotNull($this->interceptor->lastResponse); self::assertNotNull($this->interceptor->lastResponse->getEagerWorkflowTask()); } + + protected function setUp(): void + { + $this->interceptor = new CatchStartWorkflowExecutionResponseInterceptor(); + parent::setUp(); + } } #[WorkflowInterface] class FeatureWorkflow { #[WorkflowMethod('Harness_EagerWorkflow_SuccessfulStart')] - public function run() + public function run(): string { - return EXPECTED_RESULT; + return 'ok'; } } /** * Catches {@see StartWorkflowExecutionResponse} from the gRPC calls. */ -class grpcCallInterceptor implements GrpcClientInterceptor +class CatchStartWorkflowExecutionResponseInterceptor implements GrpcClientInterceptor { public ?StartWorkflowExecutionResponse $lastResponse = null; diff --git a/tests/Acceptance/Harness/Query/SuccessfulQueryTest.php b/tests/Acceptance/Harness/Query/SuccessfulQueryTest.php index 3c2bf0dd3..0232c1cbf 100644 --- a/tests/Acceptance/Harness/Query/SuccessfulQueryTest.php +++ b/tests/Acceptance/Harness/Query/SuccessfulQueryTest.php @@ -17,8 +17,10 @@ class SuccessfulQueryTest extends TestCase { #[Test] - public static function check(#[Stub('Harness_Query_SuccessfulQuery')]WorkflowStubInterface $stub): void - { + public static function check( + #[Stub('Harness_Query_SuccessfulQuery')] + WorkflowStubInterface $stub, + ): void { self::assertSame(0, $stub->query('get_counter')?->getValue(0)); $stub->signal('inc_counter'); diff --git a/tests/Acceptance/Harness/Query/UnexpectedArgumentsTest.php b/tests/Acceptance/Harness/Query/UnexpectedArgumentsTest.php index 157f95f19..35d533141 100644 --- a/tests/Acceptance/Harness/Query/UnexpectedArgumentsTest.php +++ b/tests/Acceptance/Harness/Query/UnexpectedArgumentsTest.php @@ -19,12 +19,13 @@ class UnexpectedArgumentsTest extends TestCase { #[Test] public static function check( - #[Stub('Harness_Query_UnexpectedArguments')]WorkflowStubInterface $stub, + #[Stub('Harness_Query_UnexpectedArguments')] + WorkflowStubInterface $stub, ): void { self::assertSame($stub->query('the_query', 42)?->getValue(0), 'got 42'); try { - $stub->query('the_query', true)?->getValue(0); + $stub->query('the_query', true); throw new \Exception('Query must fail due to unexpected argument type'); } catch (WorkflowQueryException $e) { self::assertStringContainsString( @@ -38,7 +39,7 @@ public static function check( # Not enough arg try { - $stub->query('the_query')?->getValue(0); + $stub->query('the_query'); throw new \Exception('Query must fail due to missing argument'); } catch (WorkflowQueryException $e) { self::assertStringContainsString('0 passed and exactly 1 expected', $e->getPrevious()->getMessage()); diff --git a/tests/Acceptance/Harness/Query/UnexpectedQueryTypeNameTest.php b/tests/Acceptance/Harness/Query/UnexpectedQueryTypeNameTest.php index 5dd0127ed..fa1ed130d 100644 --- a/tests/Acceptance/Harness/Query/UnexpectedQueryTypeNameTest.php +++ b/tests/Acceptance/Harness/Query/UnexpectedQueryTypeNameTest.php @@ -18,7 +18,8 @@ class UnexpectedQueryTypeNameTest extends TestCase { #[Test] public static function check( - #[Stub('Harness_Query_UnexpectedQueryTypeName')]WorkflowStubInterface $stub, + #[Stub('Harness_Query_UnexpectedQueryTypeName')] + WorkflowStubInterface $stub, ): void { try { $stub->query('nonexistent'); diff --git a/tests/Acceptance/Harness/Query/UnexpectedReturnTypeTest.php b/tests/Acceptance/Harness/Query/UnexpectedReturnTypeTest.php index e4f9f4c5a..444495474 100644 --- a/tests/Acceptance/Harness/Query/UnexpectedReturnTypeTest.php +++ b/tests/Acceptance/Harness/Query/UnexpectedReturnTypeTest.php @@ -19,7 +19,8 @@ class UnexpectedReturnTypeTest extends TestCase { #[Test] public static function check( - #[Stub('Harness_Query_UnexpectedReturnType')]WorkflowStubInterface $stub, + #[Stub('Harness_Query_UnexpectedReturnType')] + WorkflowStubInterface $stub, ): void { try { $stub->query('the_query')?->getValue(0, 'int'); diff --git a/tests/Acceptance/Harness/Schedule/BackfillTest.php b/tests/Acceptance/Harness/Schedule/BackfillTest.php index cc96b4126..0d9377537 100644 --- a/tests/Acceptance/Harness/Schedule/BackfillTest.php +++ b/tests/Acceptance/Harness/Schedule/BackfillTest.php @@ -39,13 +39,13 @@ public static function check( StartWorkflowAction::new('Harness_Schedule_Backfill') ->withWorkflowId($workflowId) ->withTaskQueue($feature->taskQueue) - ->withInput(['arg1']) + ->withInput(['arg1']), )->withSpec( ScheduleSpec::new() - ->withIntervalList(CarbonInterval::minute(1)) + ->withIntervalList(CarbonInterval::minute(1)), )->withState( ScheduleState::new() - ->withPaused(true) + ->withPaused(true), ), options: ScheduleOptions::new() // todo: should namespace be inherited from Service Client options by default?