Skip to content

Commit ead4329

Browse files
committed
Assert worker control-plane parity fixtures
Assert worker control-plane parity fixtures
1 parent c42131c commit ead4329

5 files changed

Lines changed: 375 additions & 0 deletions

File tree

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Commands;
6+
7+
use DurableWorkflow\Cli\Commands\WorkerCommand\DeregisterCommand;
8+
use DurableWorkflow\Cli\Commands\WorkerCommand\DescribeCommand;
9+
use DurableWorkflow\Cli\Commands\WorkerCommand\ListCommand;
10+
use DurableWorkflow\Cli\Commands\WorkerCommand\RegisterCommand;
11+
use DurableWorkflow\Cli\Support\ServerClient;
12+
use PHPUnit\Framework\TestCase;
13+
use Symfony\Component\Console\Command\Command;
14+
use Symfony\Component\Console\Tester\CommandTester;
15+
16+
final class WorkerParityFixtureTest extends TestCase
17+
{
18+
public function test_worker_register_matches_polyglot_request_fixture(): void
19+
{
20+
$fixture = self::fixture('worker-register-parity.json', 'worker.register');
21+
$client = new WorkerParityClient($fixture['response_body']);
22+
$command = new RegisterCommand();
23+
$command->setServerClient($client);
24+
25+
$tester = new CommandTester($command);
26+
27+
self::assertSame(Command::SUCCESS, $tester->execute($fixture['cli']['argv']));
28+
29+
self::assertSame($fixture['request']['method'], $client->lastMethod);
30+
self::assertSame($fixture['request']['path'], $client->lastPath);
31+
self::assertSame($fixture['request']['body'], $client->lastBody);
32+
33+
$decoded = json_decode($tester->getDisplay(), true, flags: JSON_THROW_ON_ERROR);
34+
self::assertSame($fixture['response_body'], $decoded);
35+
self::assertSame($fixture['semantic_body']['worker_id'], $decoded['worker_id'] ?? null);
36+
}
37+
38+
public function test_worker_list_matches_polyglot_request_fixture(): void
39+
{
40+
$fixture = self::fixture('worker-list-parity.json', 'worker.list');
41+
$client = new WorkerParityClient($fixture['response_body']);
42+
$command = new ListCommand();
43+
$command->setServerClient($client);
44+
45+
$tester = new CommandTester($command);
46+
47+
self::assertSame(Command::SUCCESS, $tester->execute($fixture['cli']['argv']));
48+
49+
self::assertSame($fixture['request']['method'], $client->lastMethod);
50+
self::assertSame($fixture['request']['path'], $client->lastPath);
51+
self::assertSame($fixture['request']['query'], $client->lastQuery);
52+
53+
$decoded = json_decode($tester->getDisplay(), true, flags: JSON_THROW_ON_ERROR);
54+
self::assertSame($fixture['response_body'], $decoded);
55+
self::assertSame($fixture['semantic_body']['worker_ids'], array_column($decoded['workers'], 'worker_id'));
56+
}
57+
58+
public function test_worker_describe_matches_polyglot_request_fixture(): void
59+
{
60+
$fixture = self::fixture('worker-describe-parity.json', 'worker.describe');
61+
$client = new WorkerParityClient($fixture['response_body']);
62+
$command = new DescribeCommand();
63+
$command->setServerClient($client);
64+
65+
$tester = new CommandTester($command);
66+
67+
self::assertSame(Command::SUCCESS, $tester->execute($fixture['cli']['argv']));
68+
69+
self::assertSame($fixture['request']['method'], $client->lastMethod);
70+
self::assertSame($fixture['request']['path'], $client->lastPath);
71+
72+
$decoded = json_decode($tester->getDisplay(), true, flags: JSON_THROW_ON_ERROR);
73+
self::assertSame($fixture['response_body'], $decoded);
74+
self::assertSame($fixture['semantic_body']['worker_id'], $decoded['worker_id'] ?? null);
75+
self::assertSame($fixture['semantic_body']['status'], $decoded['status'] ?? null);
76+
}
77+
78+
public function test_worker_deregister_matches_polyglot_request_fixture(): void
79+
{
80+
$fixture = self::fixture('worker-deregister-parity.json', 'worker.deregister');
81+
$client = new WorkerParityClient($fixture['response_body']);
82+
$command = new DeregisterCommand();
83+
$command->setServerClient($client);
84+
85+
$tester = new CommandTester($command);
86+
87+
self::assertSame(Command::SUCCESS, $tester->execute($fixture['cli']['argv']));
88+
89+
self::assertSame($fixture['request']['method'], $client->lastMethod);
90+
self::assertSame($fixture['request']['path'], $client->lastPath);
91+
92+
$decoded = json_decode($tester->getDisplay(), true, flags: JSON_THROW_ON_ERROR);
93+
self::assertSame($fixture['response_body'], $decoded);
94+
self::assertSame($fixture['semantic_body']['outcome'], $decoded['outcome'] ?? null);
95+
}
96+
97+
/**
98+
* @return array<string, mixed>
99+
*/
100+
private static function fixture(string $file, string $operation): array
101+
{
102+
$contents = file_get_contents(__DIR__.'/../fixtures/control-plane/'.$file);
103+
self::assertIsString($contents);
104+
105+
$fixture = json_decode($contents, true, flags: JSON_THROW_ON_ERROR);
106+
self::assertIsArray($fixture);
107+
self::assertSame('durable-workflow.polyglot.control-plane-request-fixture', $fixture['schema'] ?? null);
108+
self::assertSame($operation, $fixture['operation'] ?? null);
109+
110+
return $fixture;
111+
}
112+
}
113+
114+
final class WorkerParityClient extends ServerClient
115+
{
116+
public ?string $lastMethod = null;
117+
118+
public ?string $lastPath = null;
119+
120+
/**
121+
* @var array<string, mixed>
122+
*/
123+
public array $lastQuery = [];
124+
125+
/**
126+
* @var array<string, mixed>
127+
*/
128+
public array $lastBody = [];
129+
130+
/**
131+
* @param array<string, mixed> $response
132+
*/
133+
public function __construct(private readonly array $response)
134+
{
135+
parent::__construct('http://localhost');
136+
}
137+
138+
public function get(string $path, array $query = []): array
139+
{
140+
$this->lastMethod = 'GET';
141+
$this->lastPath = $path;
142+
$this->lastQuery = $query;
143+
144+
return $this->response;
145+
}
146+
147+
public function post(string $path, array $body = []): array
148+
{
149+
$this->lastMethod = 'POST';
150+
$this->lastPath = $path;
151+
$this->lastBody = $body;
152+
153+
return $this->response;
154+
}
155+
156+
public function delete(string $path): array
157+
{
158+
$this->lastMethod = 'DELETE';
159+
$this->lastPath = $path;
160+
$this->lastBody = [];
161+
162+
return $this->response;
163+
}
164+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"schema": "durable-workflow.polyglot.control-plane-request-fixture",
3+
"version": 1,
4+
"operation": "worker.deregister",
5+
"request": {
6+
"method": "DELETE",
7+
"path": "/workers/polyglot-worker-231"
8+
},
9+
"semantic_body": {
10+
"worker_id": "polyglot-worker-231",
11+
"outcome": "deregistered"
12+
},
13+
"response_body": {
14+
"worker_id": "polyglot-worker-231",
15+
"outcome": "deregistered"
16+
},
17+
"cli": {
18+
"argv": {
19+
"worker-id": "polyglot-worker-231",
20+
"--json": true
21+
}
22+
},
23+
"sdk_python": {
24+
"method": "deregister_worker",
25+
"args": {
26+
"worker_id": "polyglot-worker-231"
27+
}
28+
}
29+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"schema": "durable-workflow.polyglot.control-plane-request-fixture",
3+
"version": 1,
4+
"operation": "worker.describe",
5+
"request": {
6+
"method": "GET",
7+
"path": "/workers/polyglot-worker-231"
8+
},
9+
"semantic_body": {
10+
"worker_id": "polyglot-worker-231",
11+
"namespace": "orders-prod",
12+
"task_queue": "external-workflows",
13+
"runtime": "external",
14+
"status": "active"
15+
},
16+
"response_body": {
17+
"worker_id": "polyglot-worker-231",
18+
"namespace": "orders-prod",
19+
"task_queue": "external-workflows",
20+
"runtime": "external",
21+
"sdk_version": "durable-worker/1.2.3",
22+
"build_id": "build-231",
23+
"status": "active",
24+
"max_concurrent_workflow_tasks": 4,
25+
"max_concurrent_activity_tasks": 8,
26+
"supported_workflow_types": [
27+
"orders.fulfillment"
28+
],
29+
"supported_activity_types": [
30+
"inventory.sync"
31+
],
32+
"last_heartbeat_at": "2026-04-22T09:30:00Z",
33+
"registered_at": "2026-04-22T09:00:00Z",
34+
"updated_at": "2026-04-22T09:30:00Z"
35+
},
36+
"cli": {
37+
"argv": {
38+
"worker-id": "polyglot-worker-231",
39+
"--json": true
40+
}
41+
},
42+
"sdk_python": {
43+
"method": "describe_worker",
44+
"args": {
45+
"worker_id": "polyglot-worker-231"
46+
}
47+
}
48+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"schema": "durable-workflow.polyglot.control-plane-request-fixture",
3+
"version": 1,
4+
"operation": "worker.list",
5+
"request": {
6+
"method": "GET",
7+
"path": "/workers",
8+
"query": {
9+
"task_queue": "external-workflows",
10+
"status": "active"
11+
}
12+
},
13+
"semantic_body": {
14+
"namespace": "orders-prod",
15+
"worker_ids": [
16+
"polyglot-worker-231",
17+
"batch-worker-231"
18+
],
19+
"task_queue": "external-workflows",
20+
"status": "active"
21+
},
22+
"response_body": {
23+
"namespace": "orders-prod",
24+
"workers": [
25+
{
26+
"worker_id": "polyglot-worker-231",
27+
"namespace": "orders-prod",
28+
"task_queue": "external-workflows",
29+
"runtime": "external",
30+
"sdk_version": "durable-worker/1.2.3",
31+
"build_id": "build-231",
32+
"status": "active",
33+
"last_heartbeat_at": "2026-04-22T09:30:00Z"
34+
},
35+
{
36+
"worker_id": "batch-worker-231",
37+
"namespace": "orders-prod",
38+
"task_queue": "external-workflows",
39+
"runtime": "worker-runtime",
40+
"sdk_version": "polyglot-sdk/2.0.0",
41+
"build_id": "build-232",
42+
"status": "active",
43+
"last_heartbeat_at": "2026-04-22T09:31:00Z"
44+
}
45+
]
46+
},
47+
"cli": {
48+
"argv": {
49+
"--task-queue": "external-workflows",
50+
"--status": "active",
51+
"--json": true
52+
}
53+
},
54+
"sdk_python": {
55+
"method": "list_workers",
56+
"kwargs": {
57+
"task_queue": "external-workflows",
58+
"status": "active"
59+
}
60+
}
61+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"schema": "durable-workflow.polyglot.control-plane-request-fixture",
3+
"version": 1,
4+
"operation": "worker.register",
5+
"request": {
6+
"method": "POST",
7+
"path": "/worker/register",
8+
"body": {
9+
"worker_id": "polyglot-worker-231",
10+
"task_queue": "external-workflows",
11+
"runtime": "external",
12+
"sdk_version": "durable-worker/1.2.3",
13+
"build_id": "build-231",
14+
"supported_workflow_types": [
15+
"orders.fulfillment"
16+
],
17+
"supported_activity_types": [
18+
"inventory.sync"
19+
],
20+
"max_concurrent_workflow_tasks": 4,
21+
"max_concurrent_activity_tasks": 8
22+
}
23+
},
24+
"semantic_body": {
25+
"worker_id": "polyglot-worker-231",
26+
"task_queue": "external-workflows",
27+
"runtime": "external",
28+
"status": "active"
29+
},
30+
"response_body": {
31+
"worker_id": "polyglot-worker-231",
32+
"task_queue": "external-workflows",
33+
"runtime": "external",
34+
"status": "active",
35+
"registered": true
36+
},
37+
"cli": {
38+
"argv": {
39+
"worker-id": "polyglot-worker-231",
40+
"--task-queue": "external-workflows",
41+
"--runtime": "external",
42+
"--sdk-version": "durable-worker/1.2.3",
43+
"--build-id": "build-231",
44+
"--workflow-type": [
45+
"orders.fulfillment"
46+
],
47+
"--activity-type": [
48+
"inventory.sync"
49+
],
50+
"--max-workflow-tasks": "4",
51+
"--max-activity-tasks": "8",
52+
"--json": true
53+
}
54+
},
55+
"sdk_python": {
56+
"method": "register_worker",
57+
"kwargs": {
58+
"worker_id": "polyglot-worker-231",
59+
"task_queue": "external-workflows",
60+
"runtime": "external",
61+
"sdk_version": "durable-worker/1.2.3",
62+
"build_id": "build-231",
63+
"supported_workflow_types": [
64+
"orders.fulfillment"
65+
],
66+
"supported_activity_types": [
67+
"inventory.sync"
68+
],
69+
"max_concurrent_workflow_tasks": 4,
70+
"max_concurrent_activity_tasks": 8
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)