@@ -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
0 commit comments