Skip to content

Commit 2d16c31

Browse files
Merge pull request #139 from oriontech-me/validate/curl-api-generation
chore(flow-functionality): add @stable and harden curlApiGeneration test
2 parents 5131864 + e779583 commit 2d16c31

4 files changed

Lines changed: 128 additions & 16 deletions

File tree

QA-CHECKLIST.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
- [-] GET with session_id filter returns only messages from that session
109109

110110
#### 1.6 Integration Code Generation
111-
- [-] Generate curl for API execution
111+
- [x] Generate curl for API execution`flow-functionality/curlApiGeneration.spec.ts`
112112
- [-] Generate Python code for integration
113113
- [-] API access modal
114114

QA-SCENARIOS-GUIDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,15 +303,15 @@
303303

304304
---
305305

306-
### 5.1 Generate curl for execution `[-]`
306+
### 5.1 Generate curl for execution `[x]`
307307

308308
**Objective:** Verify that Langflow generates a valid `curl` command for flow execution.
309309

310310
**Step by step:**
311311
1. Open a flow in the editor.
312312
2. Click the "API Access" button (api-access-button).
313313
3. Select the `cURL` tab.
314-
4. Verify that the generated code contains the correct flow URL and the `curl -X POST` method.
314+
4. Verify that the generated code targets the correct flow URL and uses the `POST` HTTP method (the actual `curl` invocation may use either `-X POST` or `--request POST` depending on Langflow's snippet generator).
315315

316316
**Validation:** Generated curl code points to the correct flow endpoint.
317317

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Flow Functionality — cURL API Generation
2+
3+
**Last validated:** Langflow 1.10.x
4+
5+
---
6+
7+
## What this test validates *(required)*
8+
9+
Validates that Langflow's **API access modal** generates a valid macOS/Linux `curl` command that callers can execute against `/api/v1/run/{flow_id}` to run a flow programmatically. The test asserts the structural shape of the generated command, not just that the clipboard received some text.
10+
11+
If this breaks, integrators copying the snippet will hit malformed requests, missing headers, or a wrong URL — silently breaking the documented integration path that ships with every flow.
12+
13+
---
14+
15+
## Tags *(required)*
16+
17+
`@release` `@workspace` `@stable`
18+
19+
---
20+
21+
## Step by step *(required)*
22+
23+
1. Bootstrap the app and open the Templates page (`side_nav_options_all-templates`)
24+
2. Open the `Basic Prompting` template
25+
3. Click the publish button and open the API access item
26+
4. Switch to the `cURL` tab (`api_tab_curl`)
27+
5. Switch the platform sub-tab to `macOS/Linux` to make the output deterministic
28+
6. Click the Copy icon and read `navigator.clipboard`
29+
7. Assert the structural shape of the curl command (see Validation criterion)
30+
31+
---
32+
33+
## Validation criterion *(required)*
34+
35+
The clipboard content must satisfy **all** of the following:
36+
37+
- Starts with `curl --request POST`
38+
- Contains `--url '<base>/api/v1/run/<UUID>?stream=false'` (UUID matched by `[0-9a-f-]{36}`)
39+
- Contains `--header 'Content-Type: application/json'`
40+
- Contains `x-api-key: YOUR_API_KEY_HERE`
41+
- Contains `--data`
42+
- Contains `"input_value": "Hello"` (the default value carried over from the Basic Prompting template's ChatInput)
43+
- Contains `"session_id"` and `"output_type": "chat"`
44+
45+
A bare "clipboard is non-empty" check is insufficient — the previous version of the test was passing while silently copying the PowerShell variant.
46+
47+
---
48+
49+
## External dependencies *(required)*
50+
51+
- `src/frontend/src/modals/apiModal/utils/get-curl-code.tsx``getNewCurlCode` builds the command for both `unix` and `powershell` platforms
52+
- `src/frontend/src/modals/apiModal/codeTabs/code-tabs.tsx` — renders the cURL tab and the macOS/Linux ↔ Windows platform switch (default driven by `getOS()`)
53+
- `src/frontend/src/utils/utils.ts``getOS()` reads `navigator.platform` to pick the default platform tab
54+
- `src/backend/base/langflow/api/v1/endpoints.py` — owns `/api/v1/run/{flow_id}`; the URL shape encoded in the curl must keep matching this route
55+
56+
---
57+
58+
## What this test does not cover *(optional)*
59+
60+
- The Windows/PowerShell variant (different syntax: `$jsonData = @'...'@`, `curl.exe`, backtick line-continuation)
61+
- Tweaks payload encoding for flows with file-upload nodes (multi-step curl)
62+
- Actually executing the generated command against the running backend (covered by API tests under `api/flows/`)
63+
- The Python and JavaScript snippets in the same modal (covered by `pythonApiGeneration.spec.ts`)
64+
65+
---
66+
67+
## Preconditions *(optional)*
68+
69+
- Langflow running at `PLAYWRIGHT_BASE_URL`
70+
- No LLM credentials required — only the snippet generator is exercised
71+
- `clipboard-read` permission is granted globally in `playwright.config.ts`
72+
73+
---
74+
75+
## When to review this test *(optional)*
76+
77+
- The cURL tab gains a new platform option (e.g., a `bash`-only sub-tab) — the explicit `macOS/Linux` click may need to change
78+
- `getNewCurlCode` is refactored to drop `--request POST` in favor of `-X POST`, or to switch quoting style
79+
- The `/api/v1/run/{flow_id}` route is renamed or namespaced
80+
- The Basic Prompting template's default ChatInput value changes from `"Hello"` — the assertion `"input_value": "Hello"` will need to track it
81+
82+
---
83+
84+
## Notes *(optional)*
85+
86+
- The platform switch is required because `getOS()` is driven by `navigator.platform`, which can differ between local Chromium runs and CI runners. Without explicitly clicking macOS/Linux, the generated snippet is non-deterministic.
87+
- The previous version of this test asserted only `clipboardContent.length > 0`, which would pass even if the wrong (PowerShell) variant or empty content was copied. The structural assertions guard against that class of false positive.

tests/tests-automations/regression/flow-functionality/curlApiGeneration.spec.ts

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,46 @@ import { expect, test } from "../../../fixtures/fixtures";
22
import { awaitBootstrapTest } from "../../../helpers/other/await-bootstrap-test";
33

44
test(
5-
"curl_api_generation",
6-
{ tag: ["@release", "@api", "@workspace"] },
7-
async ({ page, context }) => {
8-
await awaitBootstrapTest(page);
5+
"user can copy a valid macOS/Linux curl command from the API access modal",
6+
{ tag: ["@release", "@workspace", "@stable"] },
7+
async ({ page }) => {
8+
await test.step("Open the Basic Prompting template", async () => {
9+
await awaitBootstrapTest(page);
10+
await page.getByTestId("side_nav_options_all-templates").click();
11+
await page.getByRole("heading", { name: "Basic Prompting" }).click();
12+
});
913

10-
await page.getByTestId("side_nav_options_all-templates").click();
11-
await page.getByRole("heading", { name: "Basic Prompting" }).click();
12-
await page.getByTestId("publish-button").click();
13-
await page.getByTestId("api-access-item").click();
14-
await page.getByTestId("api_tab_curl").click();
15-
await page.getByTestId("icon-Copy").last().click();
16-
const handle = await page.evaluateHandle(() =>
14+
await test.step("Open the API access modal on the cURL tab", async () => {
15+
await page.getByTestId("publish-button").click();
16+
await page.getByTestId("api-access-item").click();
17+
await page.getByTestId("api_tab_curl").click();
18+
});
19+
20+
await test.step("Force the macOS/Linux platform variant", async () => {
21+
await page.getByRole("tab", { name: "macOS/Linux" }).click();
22+
});
23+
24+
await test.step("Copy the generated command to the clipboard", async () => {
25+
await page.getByTestId("icon-Copy").last().click();
26+
});
27+
28+
const clipboardContent = await page.evaluate(() =>
1729
navigator.clipboard.readText(),
1830
);
19-
const clipboardContent = await handle.jsonValue();
20-
expect(clipboardContent.length).toBeGreaterThan(0);
31+
32+
await test.step("Validate the curl command structure", async () => {
33+
expect(clipboardContent).toMatch(/^curl --request POST/);
34+
expect(clipboardContent).toMatch(
35+
/--url '[^']*\/api\/v1\/run\/[0-9a-f-]{36}\?stream=false'/,
36+
);
37+
expect(clipboardContent).toContain(
38+
"--header 'Content-Type: application/json'",
39+
);
40+
expect(clipboardContent).toContain("x-api-key: YOUR_API_KEY_HERE");
41+
expect(clipboardContent).toContain("--data");
42+
expect(clipboardContent).toContain('"input_value": "Hello"');
43+
expect(clipboardContent).toContain('"session_id"');
44+
expect(clipboardContent).toContain('"output_type": "chat"');
45+
});
2146
},
2247
);

0 commit comments

Comments
 (0)