Files
dify/api/tests/unit_tests
Charles YaoandClaude Opus 5 e9599355a2 feat(dify-builder): stream the test run instead of replaying it
run_draft blocked on AppGenerateService.generate(streaming=False) and then
replayed every finished node-execution row through on_event, so the user
watched a spinner for the whole run and then saw every node light up at once.
It now consumes the streaming generator and fires on_event per frame.

Two things the plan assumed that turned out not to hold:

- The stream does NOT yield dicts. convert_stream_full_response yields dicts,
  but that runs in the Celery worker; on our side
  BaseAppGenerator.convert_to_event_stream (not overridden) re-wraps every
  event as "data: {json}\n\n" and every keep-alive as "event: ping\n\n". An
  isinstance(chunk, dict) guard would have dropped every frame. New
  stream_chunk_as_mapping accepts both shapes and rejects keep-alives.

- The streaming workflow_paused frame carries workflow_run_id where the
  blocking response carried id, so map_run_result would KeyError on it.
  Adapted at the call site via run_result_data_from_terminal_chunk;
  map_run_result's contract is unchanged for the fix/edit flows.

Session lifetime: _dispatch_generate is a plain function, so all of its
Session work is eager, and the generator it returns only reads a Redis
subscription while the run executes in a Celery worker with its own session.
The consumption loop therefore sits outside the `with` block rather than
pinning a DB connection for the length of a workflow run; a test pins this.

The node-execution read stays at the end -- map_run_result still needs those
rows for per_node[].outputs. Only the progress streams.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 15:42:25 -07:00
..
…