@@ -193,3 +193,167 @@ async def aiter_lines(self):
193
193
194
194
# Use the same consume_and_assert_stream function to verify the events
195
195
await consume_and_assert_stream (polling_response , job_id )
196
+
197
+
198
+ @pytest .mark .benchmark
199
+ async def test_cancel_build_unexpected_error (client , json_memory_chatbot_no_llm , logged_in_headers , monkeypatch ):
200
+ """Test handling of unexpected exceptions during flow build cancellation."""
201
+ # First create the flow
202
+ flow_id = await create_flow (client , json_memory_chatbot_no_llm , logged_in_headers )
203
+
204
+ # Start the build and get job_id
205
+ build_response = await build_flow (client , flow_id , logged_in_headers )
206
+ job_id = build_response ["job_id" ]
207
+ assert job_id is not None
208
+
209
+ # Mock the cancel_flow_build function to raise an unexpected exception
210
+ import langflow .api .v1 .chat
211
+
212
+ original_cancel_flow_build = langflow .api .v1 .chat .cancel_flow_build
213
+
214
+ async def mock_cancel_flow_build_with_error (* _args , ** _kwargs ):
215
+ msg = "Unexpected error during cancellation"
216
+ raise RuntimeError (msg )
217
+
218
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , mock_cancel_flow_build_with_error )
219
+
220
+ try :
221
+ # Try to cancel the build - should return 500 Internal Server Error
222
+ cancel_response = await client .post (f"api/v1/build/{ job_id } /cancel" , headers = logged_in_headers )
223
+ assert cancel_response .status_code == codes .INTERNAL_SERVER_ERROR
224
+
225
+ # Verify the error message
226
+ response_data = cancel_response .json ()
227
+ assert "detail" in response_data
228
+ assert "Unexpected error during cancellation" in response_data ["detail" ]
229
+ finally :
230
+ # Restore the original function to avoid affecting other tests
231
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , original_cancel_flow_build )
232
+
233
+
234
+ @pytest .mark .benchmark
235
+ async def test_cancel_build_success (client , json_memory_chatbot_no_llm , logged_in_headers , monkeypatch ):
236
+ """Test successful cancellation of a flow build."""
237
+ # First create the flow
238
+ flow_id = await create_flow (client , json_memory_chatbot_no_llm , logged_in_headers )
239
+
240
+ # Start the build and get job_id
241
+ build_response = await build_flow (client , flow_id , logged_in_headers )
242
+ job_id = build_response ["job_id" ]
243
+ assert job_id is not None
244
+
245
+ # Mock the cancel_flow_build function to simulate a successful cancellation
246
+ import langflow .api .v1 .chat
247
+
248
+ original_cancel_flow_build = langflow .api .v1 .chat .cancel_flow_build
249
+
250
+ async def mock_successful_cancel_flow_build (* _args , ** _kwargs ):
251
+ return True # Return True to indicate successful cancellation
252
+
253
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , mock_successful_cancel_flow_build )
254
+
255
+ try :
256
+ # Try to cancel the build (should return success)
257
+ cancel_response = await client .post (f"api/v1/build/{ job_id } /cancel" , headers = logged_in_headers )
258
+ assert cancel_response .status_code == codes .OK
259
+
260
+ # Verify the response structure indicates success
261
+ response_data = cancel_response .json ()
262
+ assert "success" in response_data
263
+ assert "message" in response_data
264
+ assert response_data ["success" ] is True
265
+ assert "cancelled successfully" in response_data ["message" ].lower ()
266
+ finally :
267
+ # Restore the original function to avoid affecting other tests
268
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , original_cancel_flow_build )
269
+
270
+
271
+ @pytest .mark .benchmark
272
+ async def test_cancel_nonexistent_build (client , logged_in_headers ):
273
+ """Test cancelling a non-existent flow build."""
274
+ # Generate a random job_id that doesn't exist
275
+ invalid_job_id = str (uuid .uuid4 ())
276
+
277
+ # Try to cancel a non-existent build
278
+ response = await client .post (f"api/v1/build/{ invalid_job_id } /cancel" , headers = logged_in_headers )
279
+ assert response .status_code == codes .NOT_FOUND
280
+ assert "No queue found for job_id" in response .json ()["detail" ]
281
+
282
+
283
+ @pytest .mark .benchmark
284
+ async def test_cancel_build_failure (client , json_memory_chatbot_no_llm , logged_in_headers , monkeypatch ):
285
+ """Test handling of cancellation failure."""
286
+ # First create the flow
287
+ flow_id = await create_flow (client , json_memory_chatbot_no_llm , logged_in_headers )
288
+
289
+ # Start the build and get job_id
290
+ build_response = await build_flow (client , flow_id , logged_in_headers )
291
+ job_id = build_response ["job_id" ]
292
+ assert job_id is not None
293
+
294
+ # Mock the cancel_flow_build function to simulate a failure
295
+ # The import path in monkeypatch should match exactly how it's imported in the application
296
+ import langflow .api .v1 .chat
297
+
298
+ original_cancel_flow_build = langflow .api .v1 .chat .cancel_flow_build
299
+
300
+ async def mock_cancel_flow_build (* _args , ** _kwargs ):
301
+ return False # Return False to indicate cancellation failure
302
+
303
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , mock_cancel_flow_build )
304
+
305
+ try :
306
+ # Try to cancel the build (should return failure but success=False)
307
+ cancel_response = await client .post (f"api/v1/build/{ job_id } /cancel" , headers = logged_in_headers )
308
+ assert cancel_response .status_code == codes .OK
309
+
310
+ # Verify the response structure indicates failure
311
+ response_data = cancel_response .json ()
312
+ assert "success" in response_data
313
+ assert "message" in response_data
314
+ assert response_data ["success" ] is False
315
+ assert "Failed to cancel" in response_data ["message" ]
316
+ finally :
317
+ # Restore the original function to avoid affecting other tests
318
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , original_cancel_flow_build )
319
+
320
+
321
+ @pytest .mark .benchmark
322
+ async def test_cancel_build_with_cancelled_error (client , json_memory_chatbot_no_llm , logged_in_headers , monkeypatch ):
323
+ """Test handling of CancelledError during cancellation (should be treated as failure)."""
324
+ # First create the flow
325
+ flow_id = await create_flow (client , json_memory_chatbot_no_llm , logged_in_headers )
326
+
327
+ # Start the build and get job_id
328
+ build_response = await build_flow (client , flow_id , logged_in_headers )
329
+ job_id = build_response ["job_id" ]
330
+ assert job_id is not None
331
+
332
+ # Mock the cancel_flow_build function to raise CancelledError
333
+ import asyncio
334
+
335
+ import langflow .api .v1 .chat
336
+
337
+ original_cancel_flow_build = langflow .api .v1 .chat .cancel_flow_build
338
+
339
+ async def mock_cancel_flow_build_with_cancelled_error (* _args , ** _kwargs ):
340
+ msg = "Task cancellation failed"
341
+ raise asyncio .CancelledError (msg )
342
+
343
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , mock_cancel_flow_build_with_cancelled_error )
344
+
345
+ try :
346
+ # Try to cancel the build - should return failure when CancelledError is raised
347
+ # since our implementation treats CancelledError as a failed cancellation
348
+ cancel_response = await client .post (f"api/v1/build/{ job_id } /cancel" , headers = logged_in_headers )
349
+ assert cancel_response .status_code == codes .OK
350
+
351
+ # Verify the response structure indicates failure
352
+ response_data = cancel_response .json ()
353
+ assert "success" in response_data
354
+ assert "message" in response_data
355
+ assert response_data ["success" ] is False
356
+ assert "failed to cancel" in response_data ["message" ].lower ()
357
+ finally :
358
+ # Restore the original function to avoid affecting other tests
359
+ monkeypatch .setattr (langflow .api .v1 .chat , "cancel_flow_build" , original_cancel_flow_build )
0 commit comments