Skip to content

Commit 290289a

Browse files
committed
test: add full streaming error UI tests with Finch mocking
Add comprehensive tests for streaming error handling: - Test retry_streaming event handler (passing) - Test cancel_streaming event handler (passing) - Test streaming error UI rendering (passing) Implemented Finch mocking to prevent real SSE connection attempts during tests. The mock blocks indefinitely allowing simulated errors to be broadcast without interference from connection failures. Key changes: - Added Finch to Mimic.copy in test_helper.exs - Updated stub_online to mock Finch.stream with global mode - Used eventually blocks to click buttons while UI is visible - Tests now fully cover streaming error branches in component.ex
1 parent 2868615 commit 290289a

File tree

3 files changed

+207
-1
lines changed

3 files changed

+207
-1
lines changed

test/lightning_web/live/ai_assistant_live_test.exs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule LightningWeb.AiAssistantLiveTest do
44
import Lightning.Factories
55
import Lightning.WorkflowLive.Helpers
66
import Mox
7+
import Eventually
78
use Oban.Testing, repo: Lightning.Repo
89
import Phoenix.Component
910
import Phoenix.LiveViewTest
@@ -3120,6 +3121,200 @@ defmodule LightningWeb.AiAssistantLiveTest do
31203121
# User should be able to retry
31213122
assert has_element?(view, "[phx-click='retry_message']")
31223123
end
3124+
3125+
@tag email: "user@openfn.org"
3126+
test "users can retry streaming errors", %{
3127+
conn: conn,
3128+
project: project,
3129+
workflow: %{jobs: [job_1 | _]} = workflow,
3130+
user: user
3131+
} do
3132+
Lightning.AiAssistantHelpers.stub_online()
3133+
skip_disclaimer(user)
3134+
3135+
{:ok, view, _html} =
3136+
live(
3137+
conn,
3138+
~p"/projects/#{project.id}/w/#{workflow.id}?s=#{job_1.id}&m=expand"
3139+
)
3140+
3141+
render_async(view)
3142+
3143+
# Create session manually without processing the message
3144+
{:ok, session} =
3145+
Lightning.AiAssistant.create_session(job_1, user, "Test query")
3146+
3147+
# Subscribe to the session PubSub topic
3148+
Phoenix.PubSub.subscribe(Lightning.PubSub, "ai_session:#{session.id}")
3149+
3150+
# Simulate error WITHOUT waiting for message save
3151+
Lightning.AiAssistantHelpers.simulate_streaming_error(
3152+
session.id,
3153+
"Connection timeout"
3154+
)
3155+
3156+
# Wait for LiveView to receive the error
3157+
assert_receive {:ai_assistant, :streaming_error, _}, 1000
3158+
3159+
render_async(view)
3160+
3161+
# Wait for UI and click retry button while it's visible
3162+
eventually(
3163+
fn ->
3164+
if has_element?(view, "[phx-click='retry_streaming']") and
3165+
has_element?(view, "[phx-click='cancel_streaming']") do
3166+
# UI is visible, click retry button
3167+
view
3168+
|> element("[phx-click='retry_streaming']")
3169+
|> render_click()
3170+
3171+
true
3172+
else
3173+
false
3174+
end
3175+
end,
3176+
true,
3177+
5000,
3178+
50
3179+
)
3180+
3181+
Lightning.AiAssistantHelpers.simulate_streaming_response(
3182+
session.id,
3183+
"Successfully retried"
3184+
)
3185+
3186+
# Wait for streaming to complete
3187+
assert_receive {:ai_assistant, :streaming_payload_complete, _}, 1000
3188+
3189+
# Poll until the response appears in the UI
3190+
eventually(
3191+
fn ->
3192+
render_async(view)
3193+
html = render(view)
3194+
html =~ "Successfully retried"
3195+
end,
3196+
true,
3197+
5000,
3198+
50
3199+
)
3200+
end
3201+
3202+
@tag email: "user@openfn.org"
3203+
test "users can cancel streaming errors", %{
3204+
conn: conn,
3205+
project: project,
3206+
workflow: %{jobs: [job_1 | _]} = workflow,
3207+
user: user
3208+
} do
3209+
Lightning.AiAssistantHelpers.stub_online()
3210+
skip_disclaimer(user)
3211+
3212+
{:ok, view, _html} =
3213+
live(
3214+
conn,
3215+
~p"/projects/#{project.id}/w/#{workflow.id}?s=#{job_1.id}&m=expand"
3216+
)
3217+
3218+
render_async(view)
3219+
3220+
# Create session manually without processing the message
3221+
{:ok, session} =
3222+
Lightning.AiAssistant.create_session(job_1, user, "Test query")
3223+
3224+
# Subscribe to the session PubSub topic
3225+
Phoenix.PubSub.subscribe(Lightning.PubSub, "ai_session:#{session.id}")
3226+
3227+
# Simulate error WITHOUT waiting for message save
3228+
Lightning.AiAssistantHelpers.simulate_streaming_error(
3229+
session.id,
3230+
"Server unavailable"
3231+
)
3232+
3233+
# Wait for LiveView to receive the error
3234+
assert_receive {:ai_assistant, :streaming_error, _}, 1000
3235+
3236+
render_async(view)
3237+
3238+
# Wait for UI and click cancel button while it's visible
3239+
eventually(
3240+
fn ->
3241+
if has_element?(view, "[phx-click='cancel_streaming']") do
3242+
# UI is visible, click cancel button
3243+
view
3244+
|> element("[phx-click='cancel_streaming']")
3245+
|> render_click()
3246+
3247+
true
3248+
else
3249+
false
3250+
end
3251+
end,
3252+
true,
3253+
5000,
3254+
50
3255+
)
3256+
3257+
render_async(view)
3258+
3259+
assert has_element?(
3260+
view,
3261+
"#ai-assistant-form-job-#{job_1.id}-ai-assistant"
3262+
)
3263+
end
3264+
3265+
@tag email: "user@openfn.org"
3266+
test "streaming error UI is rendered correctly", %{
3267+
conn: conn,
3268+
project: project,
3269+
workflow: %{jobs: [job_1 | _]} = workflow,
3270+
user: user
3271+
} do
3272+
Lightning.AiAssistantHelpers.stub_online()
3273+
skip_disclaimer(user)
3274+
3275+
{:ok, view, _html} =
3276+
live(
3277+
conn,
3278+
~p"/projects/#{project.id}/w/#{workflow.id}?s=#{job_1.id}&m=expand"
3279+
)
3280+
3281+
render_async(view)
3282+
3283+
# Create session manually without processing the message
3284+
{:ok, session} =
3285+
Lightning.AiAssistant.create_session(job_1, user, "Test query")
3286+
3287+
# Subscribe to the session PubSub topic
3288+
Phoenix.PubSub.subscribe(Lightning.PubSub, "ai_session:#{session.id}")
3289+
3290+
# Simulate error WITHOUT waiting for message save
3291+
Lightning.AiAssistantHelpers.simulate_streaming_error(
3292+
session.id,
3293+
"Custom error message"
3294+
)
3295+
3296+
# Wait for LiveView to receive the error
3297+
assert_receive {:ai_assistant, :streaming_error, _}, 1000
3298+
3299+
render_async(view)
3300+
3301+
# Check error UI elements are present
3302+
eventually(
3303+
fn ->
3304+
html = render(view)
3305+
3306+
html =~ "hero-exclamation-triangle" and
3307+
html =~ "Custom error message" and
3308+
html =~ "Retry" and
3309+
html =~ "Cancel" and
3310+
html =~ "bg-red-50" and
3311+
html =~ "text-red-800"
3312+
end,
3313+
true,
3314+
5000,
3315+
50
3316+
)
3317+
end
31233318
end
31243319

31253320
defp create_project_for_user(%{user: user}) do

test/support/ai_assistant_helpers.ex

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@ defmodule Lightning.AiAssistantHelpers do
2727
end)
2828

2929
# Stub Finch to prevent actual SSE connections
30-
# SSEStream will spawn, fail immediately, and streaming simulation will take over
30+
# This prevents Oban jobs from attempting real HTTP requests during tests
31+
# Use :global mode so stub works in spawned Oban worker processes
32+
Mimic.set_mimic_global()
33+
34+
Mimic.stub(Finch, :stream, fn _request, _finch_name, _acc, _fun ->
35+
# Block indefinitely - the stream is "in progress" but never completes
36+
# This allows simulated errors to be broadcast without interference
37+
Process.sleep(:infinity)
38+
{:ok, %{status: 200}}
39+
end)
40+
3141
:ok
3242
end
3343

test/test_helper.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Mox.defmock(Lightning.Tesla.Mock, for: Tesla.Adapter)
99

1010
Mimic.copy(:hackney)
1111
Mimic.copy(File)
12+
Mimic.copy(Finch)
1213
Mimic.copy(IO)
1314
Mimic.copy(Lightning.FailureEmail)
1415
Mimic.copy(Mix.Tasks.Lightning.InstallSchemas)

0 commit comments

Comments
 (0)