From fb68372672ff409ed86e4ada679cc590f599e533 Mon Sep 17 00:00:00 2001 From: "homeboy-ci[bot]" <266378653+homeboy-ci[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:50:01 +0000 Subject: [PATCH] test: prove sibling flow creation through REST --- tests/Unit/Api/Flows/FlowsEndpointTest.php | 34 ++++++++++++++++++++++ tests/fuzz/pipeline-builder-runtime.json | 6 ++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Api/Flows/FlowsEndpointTest.php b/tests/Unit/Api/Flows/FlowsEndpointTest.php index decc388a1..cd5506bd4 100644 --- a/tests/Unit/Api/Flows/FlowsEndpointTest.php +++ b/tests/Unit/Api/Flows/FlowsEndpointTest.php @@ -72,6 +72,40 @@ public function test_get_flows_for_pipeline(): void { $this->assertGreaterThan(0, count($data['data']['flows'])); } + public function test_repeated_rest_creation_preserves_distinct_sibling_flows(): void { + $created_ids = array(); + + foreach ( array( 'First REST sibling', 'Second REST sibling' ) as $flow_name ) { + $request = new WP_REST_Request( 'POST', '/datamachine/v1/flows' ); + $request->set_body_params( + array( + 'pipeline_id' => $this->test_pipeline_id, + 'flow_name' => $flow_name, + ) + ); + + $response = rest_do_request( $request ); + $data = $response->get_data(); + + $this->assertSame( 200, $response->get_status() ); + $this->assertTrue( $data['success'] ); + $this->assertArrayNotHasKey( 'code', $data ); + $created_ids[] = (int) $data['data']['flow_id']; + } + + $this->assertCount( 2, array_unique( $created_ids ) ); + + $request = new WP_REST_Request( 'GET', '/datamachine/v1/flows' ); + $request->set_param( 'pipeline_id', $this->test_pipeline_id ); + $response = rest_do_request( $request ); + $data = $response->get_data(); + $flow_ids = array_map( 'intval', array_column( $data['data']['flows'], 'flow_id' ) ); + + $this->assertSame( 200, $response->get_status() ); + $this->assertTrue( $data['success'] ); + $this->assertEmpty( array_diff( $created_ids, $flow_ids ) ); + } + public function test_pagination(): void { $request = new WP_REST_Request('GET', '/datamachine/v1/flows'); $request->set_param('pipeline_id', $this->test_pipeline_id); diff --git a/tests/fuzz/pipeline-builder-runtime.json b/tests/fuzz/pipeline-builder-runtime.json index 73d787ad8..90e3452c7 100644 --- a/tests/fuzz/pipeline-builder-runtime.json +++ b/tests/fuzz/pipeline-builder-runtime.json @@ -70,15 +70,15 @@ }, { "id": "flow-sibling-preservation", - "description": "A case-local pipeline creates two sibling flows and preserves their list after reload, replaying the current flow creation failure.", - "metadata": { "seed": 321904, "issue": 3254, "dimensions": ["flow-create", "flow-sibling-preservation", "reload", "known-product-defect-replay"] }, + "description": "A case-local pipeline creates two distinct sibling flows through successful REST requests and preserves both after reload.", + "metadata": { "seed": 321904, "issue": 3254, "dimensions": ["flow-create", "rest-success", "distinct-flow-ids", "flow-sibling-preservation", "reload", "regression"] }, "phases": { "setup": [ { "command": "wordpress.ensure-plugin-active", "args": ["plugin=data-machine/data-machine.php"] }, { "command": "wp-codebox.checkpoint-create", "args": ["name=flow-sibling-preservation"] }, { "command": "wordpress.browser-actions", "args": ["auth=wordpress-admin", "step-timeout=30s", "steps-json=[{\"kind\":\"navigate\",\"url\":\"/wp-admin/admin.php?page=datamachine-pipelines\"},{\"kind\":\"waitFor\",\"selector\":\".datamachine-pipelines-layout\"},{\"kind\":\"click\",\"text\":\"Add New Pipeline\"},{\"kind\":\"waitFor\",\"selector\":\".datamachine-flow-card--empty button\"}]", "capture=steps,console,errors"] } ], - "action": [{ "command": "wordpress.browser-actions", "args": ["auth=wordpress-admin", "step-timeout=30s", "steps-json=[{\"kind\":\"navigate\",\"url\":\"/wp-admin/admin.php?page=datamachine-pipelines\"},{\"kind\":\"waitFor\",\"selector\":\".datamachine-flow-card--empty button\"},{\"kind\":\"click\",\"selector\":\".datamachine-flow-card--empty button\"},{\"kind\":\"waitFor\",\"waitFor\":\"duration\",\"duration\":\"500ms\"},{\"kind\":\"click\",\"selector\":\".datamachine-flow-card--empty button\"},{\"kind\":\"navigate\",\"url\":\"/wp-admin/admin.php?page=datamachine-pipelines\",\"waitFor\":\"networkidle\"},{\"kind\":\"expect\",\"selector\":\".datamachine-flows-list\",\"state\":\"visible\"},{\"kind\":\"assertObservation\",\"assertion\":\"no-console-errors\"},{\"kind\":\"assertObservation\",\"assertion\":\"no-page-errors\"}]", "capture=steps,console,errors,network,screenshot"] }], + "action": [{ "command": "wordpress.browser-actions", "args": ["auth=wordpress-admin", "step-timeout=30s", "steps-json=[{\"kind\":\"navigate\",\"url\":\"/wp-admin/admin.php?page=datamachine-pipelines\"},{\"kind\":\"waitFor\",\"selector\":\".datamachine-flow-card--empty button\"},{\"kind\":\"evaluate\",\"expression\":\"return (() => { window.__datamachineFlowCreates = []; const originalFetch = window.fetch.bind(window); window.fetch = async (input, init = {}) => { const response = await originalFetch(input, init); const url = typeof input === 'string' ? input : input.url; const method = init.method || (typeof input === 'object' ? input.method : 'GET'); if (url.includes('/datamachine/v1/flows') && method.toUpperCase() === 'POST') { window.__datamachineFlowCreates.push({ status: response.status, data: await response.clone().json() }); } return response; }; })();\"},{\"kind\":\"click\",\"selector\":\".datamachine-flow-card--empty button\"},{\"kind\":\"waitFor\",\"waitFor\":\"duration\",\"duration\":\"500ms\"},{\"kind\":\"click\",\"selector\":\".datamachine-flow-card--empty button\"},{\"kind\":\"waitFor\",\"waitFor\":\"duration\",\"duration\":\"500ms\"},{\"kind\":\"evaluate\",\"expression\":\"return (() => { const creates = window.__datamachineFlowCreates; if (creates.length !== 2) throw new Error('Expected two flow POST responses, received ' + creates.length); if (creates.some(({ status }) => status < 200 || status >= 300)) throw new Error('Flow POST returned non-2xx: ' + JSON.stringify(creates)); if (creates.some(({ data }) => data.code === 'flow_creation_failed')) throw new Error('Flow creation returned flow_creation_failed'); const ids = creates.map(({ data }) => data.data.flow_id); if (new Set(ids).size !== 2) throw new Error('Flow POSTs did not return distinct IDs: ' + JSON.stringify(ids)); return ids; })();\"},{\"kind\":\"navigate\",\"url\":\"/wp-admin/admin.php?page=datamachine-pipelines\",\"waitFor\":\"networkidle\"},{\"kind\":\"expect\",\"selector\":\".datamachine-flows-list\",\"state\":\"visible\"},{\"kind\":\"evaluate\",\"expression\":\"return (() => { const siblings = document.querySelectorAll('.datamachine-flows-list .datamachine-flow-card'); if (siblings.length !== 2) throw new Error('Expected two sibling flows after reload, found ' + siblings.length); return siblings.length; })();\"},{\"kind\":\"assertObservation\",\"assertion\":\"no-console-errors\"},{\"kind\":\"assertObservation\",\"assertion\":\"no-page-errors\"}]", "capture=steps,console,errors,network,screenshot"] }], "teardown": [{ "command": "wp-codebox.checkpoint-restore", "args": ["name=flow-sibling-preservation"] }] } },