Skip to content

Commit 237203c

Browse files
test(deployments): add comprehensive frontend test suite for WXO deployments (#12535)
* test(deployments): add unit tests for all deployment API query and mutation hooks 16 test files covering deployment queries, mutations, provider accounts, execution hooks, and env var detection (59 tests total). * test(deployments): add stepper context create-mode tests and expand edit-mode/tool-naming coverage Step 2 of the frontend deployment test plan — 63 new tests (93 total) covering create-mode payload builders, step validation, provider selection, multi-flow scenarios, partial update payloads, detach/re-attach flows, and tool naming edge cases. * test(deployments): add component rendering tests for Step 3 (163 tests, 10 files) Covers tables, stepper steps, connection panel, and modals with data-testid additions to source components for reliable targeting. * test(deployments): add custom hook unit tests for Step 4 (121 tests, 7 files) Covers useErrorAlert, useProviderFilter, useNavigateToTest, useDeleteWithConfirmation, useTestDeploymentModal, useDeploymentChat (polling, thread_id, timeouts, tool traces), and watsonx-result-parsers (extractText, extractToolTraces, extractThreadId). * test(deployments): add deploy button and choice dialog unit tests for Step 5 (76 tests, 3 files) - deploy-button.test.tsx (12 tests): feature-flag rendering, disabled states (no flow/preparing/dialog open), handleDeploy click, animate-pulse on icon - deploy-choice-dialog/index.test.tsx (30 tests): phase transitions (provider → deployments → review → update), auto-select single attachment, provider key mapping, patchSnapshot args, update/error flows, onUpdateComplete - deploy-choice-dialog/hooks/use-prepare-deploy.test.ts (34 tests): handleDeploy save/snapshot/provider fetch, no-flow bail-out, deployModal vs choiceDialog branching, error handling, handleChooseNew, handleUpdateComplete, resetChoiceState * test(deployments): add E2E Playwright tests for Step 6 (28 tests, 5 files) - deployments-page.spec.ts: page nav, empty/loaded states (5 tests) - deployment-create.spec.ts: full create wizard, POST, deploy status (6 tests) - deployment-edit.spec.ts: edit mode, PATCH, cancel (5 tests) - deployment-providers.spec.ts: add/delete providers, confirmation (6 tests) - deployment-test-modal.spec.ts: chat, polling, multi-turn, reset (6 tests) Add shared deployment-mocks.ts for mock data reuse across all specs. Add data-testid to stepper modal title, add-provider modal title, and test-deployment modal title to avoid strict-mode selector violations. Add data-testid to provider radio items in step-provider.tsx. Set LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true in CI workflow env. * fix(tests): update deployment E2E mocks for wxo-fe API changes - provider_accounts field rename: { providers } → { provider_accounts } - ProviderAccount.provider_url → ProviderAccount.url - Execution endpoints moved to deployment-scoped URLs: POST /deployments/executions → /deployments/{id}/executions GET /deployments/executions/{exec_id} → /deployments/{id}/executions/{exec_id} - Remove deployment_id from POST execution request body (now in URL path) * fix(tests): update unit tests for wxo-fe API shape changes - ProviderAccount: provider_url → url, removed provider_tenant_id - Provider list response: { providers } → { provider_accounts } - ProviderCredentials: provider_url → url - Deployment payload: spec.{name,description,type} → top-level fields - Provider data: operations → add_flows/upsert_flows/remove_flows - Connections: raw_payloads[].environment_variables → connections[].credentials - DeploymentConfigItem: { id, name } → { app_id, connection_id } * fix(tests): align frontend tests with revised deployments API shape Update unit tests, E2E mocks, and the ProviderAccountListResponse type to match the API changes from the v1 deployments revision (#12478): - execution endpoints now use deployment_id in URL path - getExecution uses deployment_id + execution_id (no provider_id) - provider accounts response key changed to provider_accounts - deployment configs response wrapped in provider_data - deploy-choice-dialog now uses useGetDeploymentAttachments * fix(ci): read LANGFLOW_FEATURE_WXO_DEPLOYMENTS from process.env fallback Vite config only read feature flags from the .env file via dotenv, ignoring CI workflow environment variables. This caused all 28 deployment E2E tests to fail because the flag was never enabled. * fix(tests): add missing resource_key to deployment mapper test mocks The SimpleNamespace mocks in TestCreateResponse and TestMapperUpdateResult were missing the resource_key attribute now required by the mapper. * test(deployments): add E2E tests for type-to-confirm deployment deletion Adds testid to the delete dropdown item and four Playwright tests covering: - dialog opens on delete action - confirm button disabled until name matches exactly - confirming with correct name calls DELETE /api/v1/deployments/{id} - cancel dismisses without calling DELETE
1 parent 2692259 commit 237203c

55 files changed

Lines changed: 10492 additions & 11 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/typescript_test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ env:
7171
# This path is used for caching across workflows
7272
PLAYWRIGHT_BROWSERS_PATH: "ms-playwright"
7373
PLAYWRIGHT_VERSION: "1.57.0"
74+
LANGFLOW_FEATURE_WXO_DEPLOYMENTS: "true"
7475

7576
jobs:
7677
determine-test-suite:

src/backend/tests/unit/api/v1/test_deployment_description_and_type.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def test_surfaces_description_from_db_row(self):
126126
provider_account_id = uuid4()
127127
row = SimpleNamespace(
128128
id=uuid4(),
129+
resource_key="rk-1",
129130
deployment_provider_account_id=provider_account_id,
130131
name="deploy",
131132
description="my description",
@@ -145,6 +146,7 @@ def test_description_none_when_not_set(self):
145146
provider_account_id = uuid4()
146147
row = SimpleNamespace(
147148
id=uuid4(),
149+
resource_key="rk-1",
148150
deployment_provider_account_id=provider_account_id,
149151
name="deploy",
150152
description=None,
@@ -164,6 +166,7 @@ def test_uses_enum_type_from_row(self):
164166
provider_account_id = uuid4()
165167
row = SimpleNamespace(
166168
id=uuid4(),
169+
resource_key="rk-1",
167170
deployment_provider_account_id=provider_account_id,
168171
name="deploy",
169172
description=None,
@@ -190,6 +193,7 @@ def test_reads_description_from_row(self):
190193
provider_account_id = uuid4()
191194
row = SimpleNamespace(
192195
id=uuid4(),
196+
resource_key="rk-1",
193197
deployment_provider_account_id=provider_account_id,
194198
name="deploy",
195199
description="persisted desc",
@@ -210,6 +214,7 @@ def test_description_none_from_row(self):
210214
provider_account_id = uuid4()
211215
row = SimpleNamespace(
212216
id=uuid4(),
217+
resource_key="rk-1",
213218
deployment_provider_account_id=provider_account_id,
214219
name="deploy",
215220
description=None,
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { render, screen } from "@testing-library/react";
2+
import userEvent from "@testing-library/user-event";
3+
4+
// ---------------------------------------------------------------------------
5+
// Mocks
6+
// ---------------------------------------------------------------------------
7+
8+
let mockCurrentFlowId: string | undefined = "flow-1";
9+
let mockIsPreparingDeploy = false;
10+
let mockChoiceDialogOpen = false;
11+
let mockDeployModalOpen = false;
12+
const mockHandleDeploy = jest.fn();
13+
const mockSetChoiceDialogOpen = jest.fn();
14+
const mockSetDeployModalOpen = jest.fn();
15+
16+
jest.mock("@/customization/feature-flags", () => ({
17+
ENABLE_DEPLOYMENTS: true,
18+
}));
19+
20+
jest.mock("../deploy-choice-dialog/hooks/use-prepare-deploy", () => ({
21+
usePrepareDeploy: () => ({
22+
currentFlowId: mockCurrentFlowId,
23+
isPreparingDeploy: mockIsPreparingDeploy,
24+
choiceDialogOpen: mockChoiceDialogOpen,
25+
setChoiceDialogOpen: mockSetChoiceDialogOpen,
26+
deployModalOpen: mockDeployModalOpen,
27+
setDeployModalOpen: mockSetDeployModalOpen,
28+
providers: [],
29+
pendingSnapshotVersionId: "",
30+
initialVersionByFlow: new Map(),
31+
stepperInitialProvider: undefined,
32+
stepperInitialInstance: undefined,
33+
handleDeploy: mockHandleDeploy,
34+
handleChooseNew: jest.fn(),
35+
handleUpdateComplete: jest.fn(),
36+
resetChoiceState: jest.fn(),
37+
}),
38+
}));
39+
40+
jest.mock(
41+
"@/pages/MainPage/pages/deploymentsPage/hooks/use-navigate-to-test",
42+
() => ({
43+
useNavigateToTest: () => jest.fn(),
44+
}),
45+
);
46+
47+
jest.mock("../deploy-choice-dialog", () => ({
48+
__esModule: true,
49+
default: () => null,
50+
}));
51+
52+
jest.mock(
53+
"@/pages/MainPage/pages/deploymentsPage/components/deployment-stepper-modal",
54+
() => ({
55+
__esModule: true,
56+
default: () => null,
57+
}),
58+
);
59+
60+
// Override global mock to forward className so animate-pulse can be tested
61+
jest.mock("@/components/common/genericIconComponent", () => ({
62+
__esModule: true,
63+
default: ({ name, className }: { name: string; className?: string }) => (
64+
<span data-testid={`icon-${name}`} className={className ?? ""} />
65+
),
66+
}));
67+
68+
import DeployButton from "../deploy-button";
69+
70+
// ---------------------------------------------------------------------------
71+
// Setup
72+
// ---------------------------------------------------------------------------
73+
74+
beforeEach(() => {
75+
jest.clearAllMocks();
76+
mockCurrentFlowId = "flow-1";
77+
mockIsPreparingDeploy = false;
78+
mockChoiceDialogOpen = false;
79+
mockDeployModalOpen = false;
80+
});
81+
82+
// ---------------------------------------------------------------------------
83+
// Tests
84+
// ---------------------------------------------------------------------------
85+
86+
describe("DeployButton — rendering", () => {
87+
it("renders the deploy button with data-testid", () => {
88+
render(<DeployButton />);
89+
90+
expect(screen.getByTestId("deploy-btn-flow")).toBeInTheDocument();
91+
});
92+
93+
it("renders Deploy text label", () => {
94+
render(<DeployButton />);
95+
96+
expect(screen.getByText("Deploy")).toBeInTheDocument();
97+
});
98+
99+
it("renders the Rocket icon", () => {
100+
render(<DeployButton />);
101+
102+
expect(screen.getByTestId("icon-Rocket")).toBeInTheDocument();
103+
});
104+
});
105+
106+
describe("DeployButton — disabled states", () => {
107+
it("is enabled when currentFlowId is set and nothing is busy", () => {
108+
render(<DeployButton />);
109+
110+
expect(screen.getByTestId("deploy-btn-flow")).not.toBeDisabled();
111+
});
112+
113+
it("is disabled when currentFlowId is undefined", () => {
114+
mockCurrentFlowId = undefined;
115+
render(<DeployButton />);
116+
117+
expect(screen.getByTestId("deploy-btn-flow")).toBeDisabled();
118+
});
119+
120+
it("is disabled when isPreparingDeploy is true", () => {
121+
mockIsPreparingDeploy = true;
122+
render(<DeployButton />);
123+
124+
expect(screen.getByTestId("deploy-btn-flow")).toBeDisabled();
125+
});
126+
127+
it("is disabled when choiceDialogOpen is true", () => {
128+
mockChoiceDialogOpen = true;
129+
render(<DeployButton />);
130+
131+
expect(screen.getByTestId("deploy-btn-flow")).toBeDisabled();
132+
});
133+
134+
it("is disabled when deployModalOpen is true", () => {
135+
mockDeployModalOpen = true;
136+
render(<DeployButton />);
137+
138+
expect(screen.getByTestId("deploy-btn-flow")).toBeDisabled();
139+
});
140+
});
141+
142+
describe("DeployButton — click interaction", () => {
143+
it("calls handleDeploy when button is clicked", async () => {
144+
const user = userEvent.setup();
145+
render(<DeployButton />);
146+
147+
await user.click(screen.getByTestId("deploy-btn-flow"));
148+
149+
expect(mockHandleDeploy).toHaveBeenCalledTimes(1);
150+
});
151+
152+
it("does not call handleDeploy when disabled with no currentFlowId", async () => {
153+
const user = userEvent.setup();
154+
mockCurrentFlowId = undefined;
155+
render(<DeployButton />);
156+
157+
await user.click(screen.getByTestId("deploy-btn-flow"));
158+
159+
expect(mockHandleDeploy).not.toHaveBeenCalled();
160+
});
161+
162+
it("does not call handleDeploy when disabled while preparing", async () => {
163+
const user = userEvent.setup();
164+
mockIsPreparingDeploy = true;
165+
render(<DeployButton />);
166+
167+
await user.click(screen.getByTestId("deploy-btn-flow"));
168+
169+
expect(mockHandleDeploy).not.toHaveBeenCalled();
170+
});
171+
});
172+
173+
describe("DeployButton — loading pulse", () => {
174+
it("applies animate-pulse to Rocket icon when isPreparingDeploy is true", () => {
175+
mockIsPreparingDeploy = true;
176+
render(<DeployButton />);
177+
178+
expect(screen.getByTestId("icon-Rocket").className).toContain(
179+
"animate-pulse",
180+
);
181+
});
182+
183+
it("does not apply animate-pulse when isPreparingDeploy is false", () => {
184+
mockIsPreparingDeploy = false;
185+
render(<DeployButton />);
186+
187+
expect(screen.getByTestId("icon-Rocket").className).not.toContain(
188+
"animate-pulse",
189+
);
190+
});
191+
});

0 commit comments

Comments
 (0)