Skip to content

test: WorkflowExecutionListener behaviour-documenting tests#508

Open
mdproctor wants to merge 2 commits into
quarkiverse:mainfrom
mdproctor:test/workflow-execution-listener-behaviour
Open

test: WorkflowExecutionListener behaviour-documenting tests#508
mdproctor wants to merge 2 commits into
quarkiverse:mainfrom
mdproctor:test/workflow-execution-listener-behaviour

Conversation

@mdproctor
Copy link
Copy Markdown

Summary

Adds WorkflowExecutionListenerBehaviourTest — four behaviour-documenting tests for the WorkflowExecutionListener lifecycle hooks in the langchain4j module. These tests answer specific integration questions from casehubio/engine#213.

The tests are written in the discovering-documentation style: assertions lock in the actual observed behaviour and use rich messages to explain the finding and its implications. They exist to protect behaviour from silent regression, not to assert what the code should do.

Findings documented

Q1 — WorkflowContext cast + context() setter:

  • (WorkflowContext) event.workflowContext() is always safe.
  • ctx.context(WorkflowModel) setter does not propagate to FuncDSL task function input — tasks receive input from the workflow's INPUT model, not the running context(). Metadata must be passed via startInstance(Map) instead.

Q2 — onWorkflowCompleted timing and output:

  • The hook fires synchronously as part of the CompletableFuture completion chain inside startExecution()completedOutput is set before await() returns.
  • instanceData().output() returns null for simple FuncDSL single-task workflows. Listeners must null-guard before calling .asMap().

Q3 — onTaskCompleted granularity for sequential agents:

  • Documents the exact onTaskCompleted fire count for three sequential agent actions. The assertion is self-documenting and protects the count from changing unnoticed.

Q4 — Uni<T> return type end-to-end:

  • A workflow function returning Uni<String> completes correctly via Uni2CompletableFuture with no thread blocking. Confirms the async dispatch integration path works as expected.

Test plan

  • All four tests pass locally (mvn test -pl langchain4j/runtime -Dtest=WorkflowExecutionListenerBehaviourTest)
  • No existing tests broken

🤖 Generated with Claude Code

Adds WorkflowExecutionListenerBehaviourTest, a set of four
behaviour-documenting tests for the WorkflowExecutionListener lifecycle
hooks. These tests answer specific integration questions that
casehub-engine depends on (see casehubio/engine#213).

Q1 — WorkflowContext cast: confirmed safe. The context(WorkflowModel)
setter does NOT propagate to FuncDSL task function input; tasks receive
their input from the workflow INPUT model, not the running context().
casehub must pass propagation metadata via startInstance(Map) input.

Q2 — onWorkflowCompleted timing: the hook fires synchronously as part
of the CompletableFuture completion chain inside startExecution().
instanceData().output() is null for simple FuncDSL single-task
workflows — listeners must null-guard before calling asMap().

Q3 — onTaskCompleted granularity: documents the exact count observed
for three sequential agent actions — assertion is self-documenting and
protects the count from changing unnoticed.

Q4 — Uni<T> return type: a workflow function returning Uni<String>
completes correctly via Uni2CompletableFuture with no blocking. This
confirms the casehub FlowWorker ↔ WorkOrchestrator dispatch path.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@Override
public void onWorkflowCompleted(WorkflowCompletedEvent event) {
// output() can return null for simple FuncDSL single-task workflows
var outputModel = event.workflowContext().instanceData().output();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var outputModel = event.workflowContext().instanceData().output();
var outputModel = event.output();

…ertion

- onWorkflowCompleted: use event.output() — the correct API for output
  inside event callbacks. instanceData().output() is a blocking join
  intended for callers outside the event chain, not inside listeners.
- Q2: correct the finding — event.output() is populated and the hook
  fires synchronously. Remove misdiagnosed null-output claim and sleep.
- Q3: replace isEqualTo(actualCount) self-reference with
  isGreaterThanOrEqualTo(1), which actually catches hook-not-firing.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@mdproctor
Copy link
Copy Markdown
Author

Updated in response to @fjtirado's review:

  • onWorkflowCompleted: switched to event.output() as suggested — this is the correct API, populated directly from the task result before the event fires. Removed the null-guard workaround and the incorrect comment about instanceData().output().
  • Q2 javadoc + test: corrected the finding. The original test misdiagnosed a null-output "bug"; the actual finding is that event.output() carries the output correctly and the hook fires synchronously.
  • Q3 assertion: replaced isEqualTo(actualCount) (self-referential, always passes) with isGreaterThanOrEqualTo(1), which actually catches the hook-not-firing case.

Also noting for context: sdk-java#1359 (moving output() off WorkflowInstanceData) was raised as a separate PR but Javi had already fixed it independently in #1357 — that PR is closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants