Skip to content

Commit 2735512

Browse files
[cross-repo from server#390] Conformance blocker: complete search-attributes parity coverage beyond smoke (#675)
1 parent a2abede commit 2735512

3 files changed

Lines changed: 172 additions & 5 deletions

File tree

docs/api-stability.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,16 @@ processes that need to drive the standalone server control plane from PHP:
179179
- `Workflow\V2\Client\ControlPlaneClient`
180180
- `Workflow\V2\Exceptions\ControlPlaneRequestException`
181181

182-
`ControlPlaneClient` covers workflow start, workflow describe,
183-
run describe, signal delivery, query execution, and cluster-info reads
182+
`ControlPlaneClient` covers workflow start, workflow list by visibility
183+
query, workflow describe, run describe, signal delivery, query execution,
184+
search-attribute definition list/create/delete, and cluster-info reads
184185
against `POST /api/workflows`, `GET /api/workflows/{workflowId}`,
185186
`GET /api/workflows/{workflowId}/runs/{runId}`,
186187
`POST /api/workflows/{workflowId}/signal/{signalName}`,
187-
`POST /api/workflows/{workflowId}/query/{queryName}`, and their current-run
188-
targeted `/runs/{runId}` variants. It sends the
188+
`POST /api/workflows/{workflowId}/query/{queryName}`,
189+
`GET /api/workflows`, `GET|POST /api/search-attributes`, and
190+
`DELETE /api/search-attributes/{name}`, plus their current-run targeted
191+
`/runs/{runId}` variants. It sends the
189192
`X-Durable-Workflow-Control-Plane-Version` and `X-Namespace` headers on
190193
every request, accepts raw PHP argument arrays that the server resolves
191194
through the normal payload-envelope boundary, and returns the raw server

src/V2/Client/ControlPlaneClient.php

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,42 @@ public function describeWorkflowRun(string $workflowId, string $runId): array
167167
));
168168
}
169169

170+
/**
171+
* @param array<string, mixed> $filters
172+
* @return array<string, mixed>
173+
*/
174+
public function listWorkflows(array $filters = []): array
175+
{
176+
return $this->get('/workflows', $this->withoutNulls($filters));
177+
}
178+
179+
/**
180+
* @return array<string, mixed>
181+
*/
182+
public function listSearchAttributes(): array
183+
{
184+
return $this->get('/search-attributes');
185+
}
186+
187+
/**
188+
* @return array<string, mixed>
189+
*/
190+
public function createSearchAttribute(string $name, string $type): array
191+
{
192+
return $this->post('/search-attributes', [
193+
'name' => $name,
194+
'type' => $type,
195+
], [200, 201]);
196+
}
197+
198+
/**
199+
* @return array<string, mixed>
200+
*/
201+
public function deleteSearchAttribute(string $name): array
202+
{
203+
return $this->delete(sprintf('/search-attributes/%s', $this->pathSegment($name)));
204+
}
205+
170206
/**
171207
* @return array<string, mixed>
172208
*/
@@ -176,17 +212,19 @@ public function clusterInfo(): array
176212
}
177213

178214
/**
215+
* @param array<string, mixed> $query
179216
* @return array<string, mixed>
180217
*/
181218
private function get(
182219
string $path,
220+
array $query = [],
183221
?int $requestTimeoutSeconds = null,
184222
bool $enforceControlPlaneHeader = true,
185223
): array {
186224
$response = $this->http
187225
->withHeaders($this->headers())
188226
->timeout($requestTimeoutSeconds ?? $this->defaultRequestTimeoutSeconds)
189-
->get($this->url($path));
227+
->get($this->url($path), $query);
190228

191229
return $this->decode($response, $path, $enforceControlPlaneHeader);
192230
}
@@ -210,6 +248,23 @@ private function post(
210248
return $this->decode($response, $path, true, $successStatuses);
211249
}
212250

251+
/**
252+
* @param list<int> $successStatuses
253+
* @return array<string, mixed>
254+
*/
255+
private function delete(
256+
string $path,
257+
array $successStatuses = [200],
258+
?int $requestTimeoutSeconds = null,
259+
): array {
260+
$response = $this->http
261+
->withHeaders($this->headers())
262+
->timeout($requestTimeoutSeconds ?? $this->defaultRequestTimeoutSeconds)
263+
->delete($this->url($path));
264+
265+
return $this->decode($response, $path, true, $successStatuses);
266+
}
267+
213268
/**
214269
* @param list<int> $successStatuses
215270
* @return array<string, mixed>

tests/Unit/V2/ControlPlaneClientTest.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,115 @@ public function testQueryWorkflowReturnsRawServerEnvelope(): void
129129
$this->assertSame(8, $response['result']);
130130
}
131131

132+
public function testListWorkflowsSendsVisibilityQueryFilters(): void
133+
{
134+
$http = new HttpFactory();
135+
$requestQuery = [];
136+
$namespaceHeader = false;
137+
138+
$http->fake(function (Request $request) use ($http, &$requestQuery, &$namespaceHeader) {
139+
$requestQuery = $request->data();
140+
if ($requestQuery === []) {
141+
parse_str(parse_url((string) $request->url(), PHP_URL_QUERY) ?: '', $requestQuery);
142+
}
143+
$namespaceHeader = $request->hasHeader('X-Namespace', 'sa-test');
144+
145+
$this->assertSame('/api/workflows', parse_url((string) $request->url(), PHP_URL_PATH));
146+
147+
return $http->response([
148+
'workflows' => [
149+
[
150+
'workflow_id' => 'order-php-1',
151+
'search_attributes' => [
152+
'customer_id' => 'cust-8',
153+
'priority_tier' => 'platinum',
154+
],
155+
],
156+
],
157+
], 200, [
158+
ControlPlaneClient::CONTROL_PLANE_HEADER => ControlPlaneClient::CONTROL_PLANE_VERSION,
159+
]);
160+
});
161+
162+
$client = new ControlPlaneClient($http, 'http://server:8080', 'test-token', namespace: 'sa-test');
163+
$response = $client->listWorkflows([
164+
'query' => 'customer_id = "cust-2" OR customer_id = "cust-8"',
165+
'status' => 'running',
166+
'page_size' => 100,
167+
]);
168+
169+
$this->assertTrue($namespaceHeader);
170+
$this->assertSame('customer_id = "cust-2" OR customer_id = "cust-8"', $requestQuery['query']);
171+
$this->assertSame('running', $requestQuery['status']);
172+
$this->assertSame('100', (string) $requestQuery['page_size']);
173+
$this->assertSame('order-php-1', $response['workflows'][0]['workflow_id']);
174+
}
175+
176+
public function testSearchAttributeDefinitionMethodsUseControlPlaneRoutes(): void
177+
{
178+
$http = new HttpFactory();
179+
$requests = [];
180+
181+
$http->fake(function (Request $request) use ($http, &$requests) {
182+
$requests[] = [
183+
'method' => $request->method(),
184+
'url' => (string) $request->url(),
185+
'body' => $request->data(),
186+
];
187+
188+
$headers = [
189+
ControlPlaneClient::CONTROL_PLANE_HEADER => ControlPlaneClient::CONTROL_PLANE_VERSION,
190+
];
191+
192+
return match ($request->method()) {
193+
'GET' => $http->response([
194+
'custom_attributes' => [
195+
'customer_id' => 'string',
196+
],
197+
], 200, $headers),
198+
'POST' => $http->response([
199+
'name' => 'priority_tier',
200+
'type' => 'keyword',
201+
], 201, $headers),
202+
'DELETE' => $http->response([
203+
'deleted' => true,
204+
'name' => 'priority tier temp',
205+
], 200, $headers),
206+
default => $http->response([], 500, $headers),
207+
};
208+
});
209+
210+
$client = new ControlPlaneClient($http, 'http://server:8080', 'test-token');
211+
212+
$definitions = $client->listSearchAttributes();
213+
$created = $client->createSearchAttribute('priority_tier', 'keyword');
214+
$deleted = $client->deleteSearchAttribute('priority tier temp');
215+
216+
$this->assertSame('string', $definitions['custom_attributes']['customer_id']);
217+
$this->assertSame('priority_tier', $created['name']);
218+
$this->assertTrue($deleted['deleted']);
219+
$this->assertSame([
220+
[
221+
'method' => 'GET',
222+
'url' => 'http://server:8080/api/search-attributes',
223+
'body' => [],
224+
],
225+
[
226+
'method' => 'POST',
227+
'url' => 'http://server:8080/api/search-attributes',
228+
'body' => [
229+
'name' => 'priority_tier',
230+
'type' => 'keyword',
231+
],
232+
],
233+
[
234+
'method' => 'DELETE',
235+
'url' => 'http://server:8080/api/search-attributes/priority%20tier%20temp',
236+
'body' => [],
237+
],
238+
], $requests);
239+
}
240+
132241
public function testThrowsTypedExceptionForControlPlaneErrors(): void
133242
{
134243
$http = new HttpFactory();

0 commit comments

Comments
 (0)