@@ -207,16 +207,17 @@ func Test_Status_InitiallyStable(t *testing.T) {
207207func Test_Send_AddsUserMessage (t * testing.T ) {
208208 mClock := quartz .NewMock (t )
209209 mock := newMockAgentIO ()
210- // Set up blocking to synchronize with the goroutine
210+ // Set up blocking so we can inspect state mid-flight
211211 started , done := mock .BlockWrite ()
212212
213213 conv := acpio .NewACPConversation (context .Background (), mock , nil , nil , nil , mClock )
214214 conv .Start (context .Background ())
215215
216- err := conv .Send (screentracker.MessagePartText {Content : "hello" })
217- require .NoError (t , err )
216+ // Send blocks until completion, so run in a goroutine
217+ errCh := make (chan error , 1 )
218+ go func () { errCh <- conv .Send (screentracker.MessagePartText {Content : "hello" }) }()
218219
219- // Wait for the write goroutine to start
220+ // Wait for the write to start
220221 <- started
221222
222223 messages := conv .Messages ()
@@ -226,8 +227,9 @@ func Test_Send_AddsUserMessage(t *testing.T) {
226227 assert .Equal (t , "hello" , messages [0 ].Message )
227228 assert .Equal (t , screentracker .ConversationRoleAgent , messages [1 ].Role )
228229
229- // Unblock the write to let the test complete cleanly
230+ // Unblock the write to let Send complete
230231 close (done )
232+ require .NoError (t , <- errCh )
231233}
232234
233235func Test_Send_RejectsEmptyMessage (t * testing.T ) {
@@ -277,19 +279,20 @@ func Test_Send_RejectsDuplicateSend(t *testing.T) {
277279 conv := acpio .NewACPConversation (context .Background (), mock , nil , nil , nil , mClock )
278280 conv .Start (context .Background ())
279281
280- // First send should succeed
281- err := conv . Send (screentracker. MessagePartText { Content : "first" } )
282- require . NoError ( t , err )
282+ // First send blocks, so run in a goroutine
283+ errCh := make ( chan error , 1 )
284+ go func () { errCh <- conv . Send (screentracker. MessagePartText { Content : "first" }) }( )
283285
284286 // Wait for the write to start (ensuring we're in "prompting" state)
285287 <- started
286288
287289 // Second send while first is processing should fail
288- err = conv .Send (screentracker.MessagePartText {Content : "second" })
290+ err : = conv .Send (screentracker.MessagePartText {Content : "second" })
289291 assert .ErrorIs (t , err , screentracker .ErrMessageValidationChanging )
290292
291293 // Unblock the write to let the test complete cleanly
292294 close (done )
295+ require .NoError (t , <- errCh )
293296}
294297
295298func Test_Status_ChangesWhileProcessing (t * testing.T ) {
@@ -305,9 +308,9 @@ func Test_Status_ChangesWhileProcessing(t *testing.T) {
305308 conv := acpio .NewACPConversation (ctx , mock , nil , nil , emitter , mClock )
306309 conv .Start (ctx )
307310
308- // Send a message
309- err := conv . Send (screentracker. MessagePartText { Content : "test" } )
310- require . NoError ( t , err )
311+ // Send blocks, so run in a goroutine
312+ errCh := make ( chan error , 1 )
313+ go func () { errCh <- conv . Send (screentracker. MessagePartText { Content : "test" }) }( )
311314
312315 // Wait for write to start
313316 <- started
@@ -318,7 +321,8 @@ func Test_Status_ChangesWhileProcessing(t *testing.T) {
318321 // Unblock the write
319322 close (done )
320323
321- // Wait for the goroutine to complete - status should then be stable.
324+ // Wait for Send to complete - status should then be stable.
325+ require .NoError (t , <- errCh )
322326 emitter .WaitForStatus (ctx , t , screentracker .ConversationStatusStable )
323327}
324328
@@ -334,9 +338,9 @@ func Test_Text_ReturnsStreamingContent(t *testing.T) {
334338 // Initially empty
335339 assert .Equal (t , "" , conv .Text ())
336340
337- // Send a message
338- err := conv . Send (screentracker. MessagePartText { Content : "question" } )
339- require . NoError ( t , err )
341+ // Send blocks, so run in a goroutine
342+ errCh := make ( chan error , 1 )
343+ go func () { errCh <- conv . Send (screentracker. MessagePartText { Content : "question" }) }( )
340344
341345 // Wait for write to start
342346 <- started
@@ -352,8 +356,9 @@ func Test_Text_ReturnsStreamingContent(t *testing.T) {
352356 require .Len (t , messages , 2 )
353357 assert .Equal (t , "Hello world!" , messages [1 ].Message )
354358
355- // Unblock the write to let the test complete cleanly
359+ // Unblock the write to let Send complete
356360 close (done )
361+ require .NoError (t , <- errCh )
357362}
358363
359364func Test_Emitter_CalledOnChanges (t * testing.T ) {
@@ -370,9 +375,9 @@ func Test_Emitter_CalledOnChanges(t *testing.T) {
370375 conv := acpio .NewACPConversation (ctx , mock , nil , nil , emitter , mClock )
371376 conv .Start (ctx )
372377
373- // Send a message
374- err := conv . Send (screentracker. MessagePartText { Content : "test" } )
375- require . NoError ( t , err )
378+ // Send blocks, so run in a goroutine
379+ errCh := make ( chan error , 1 )
380+ go func () { errCh <- conv . Send (screentracker. MessagePartText { Content : "test" }) }( )
376381
377382 // Wait for write to start
378383 <- started
@@ -390,6 +395,7 @@ func Test_Emitter_CalledOnChanges(t *testing.T) {
390395
391396 // Unblock the write to complete processing
392397 close (done )
398+ require .NoError (t , <- errCh )
393399
394400 // Wait for completion emit
395401 emitter .WaitForStatus (ctx , t , screentracker .ConversationStatusStable )
@@ -413,7 +419,7 @@ func Test_InitialPrompt_SentOnStart(t *testing.T) {
413419 conv := acpio .NewACPConversation (context .Background (), mock , nil , initialPrompt , nil , mClock )
414420 conv .Start (context .Background ())
415421
416- // Wait for write to start (initial prompt is being sent)
422+ // Wait for write to start (initial prompt is being sent via Start's goroutine )
417423 <- started
418424
419425 // Should have user message from initial prompt
@@ -429,14 +435,15 @@ func Test_InitialPrompt_SentOnStart(t *testing.T) {
429435func Test_Messages_AreCopied (t * testing.T ) {
430436 mClock := quartz .NewMock (t )
431437 mock := newMockAgentIO ()
432- // Set up blocking to synchronize
438+ // Set up blocking so we can inspect state mid-flight
433439 started , done := mock .BlockWrite ()
434440
435441 conv := acpio .NewACPConversation (context .Background (), mock , nil , nil , nil , mClock )
436442 conv .Start (context .Background ())
437443
438- err := conv .Send (screentracker.MessagePartText {Content : "test" })
439- require .NoError (t , err )
444+ // Send blocks, so run in a goroutine
445+ errCh := make (chan error , 1 )
446+ go func () { errCh <- conv .Send (screentracker.MessagePartText {Content : "test" }) }()
440447
441448 // Wait for write to start
442449 <- started
@@ -450,8 +457,9 @@ func Test_Messages_AreCopied(t *testing.T) {
450457 originalMessages := conv .Messages ()
451458 assert .Equal (t , "test" , originalMessages [0 ].Message )
452459
453- // Unblock the write to let the test complete cleanly
460+ // Unblock the write to let Send complete
454461 close (done )
462+ require .NoError (t , <- errCh )
455463}
456464
457465func Test_ErrorRemovesPartialMessage (t * testing.T ) {
@@ -467,9 +475,9 @@ func Test_ErrorRemovesPartialMessage(t *testing.T) {
467475 conv := acpio .NewACPConversation (ctx , mock , nil , nil , emitter , mClock )
468476 conv .Start (ctx )
469477
470- // Send a message
471- err := conv . Send (screentracker. MessagePartText { Content : "test" } )
472- require . NoError ( t , err )
478+ // Send blocks, so run in a goroutine
479+ errCh := make ( chan error , 1 )
480+ go func () { errCh <- conv . Send (screentracker. MessagePartText { Content : "test" }) }( )
473481
474482 // Wait for write to start
475483 <- started
@@ -494,8 +502,8 @@ func Test_ErrorRemovesPartialMessage(t *testing.T) {
494502 mock .mu .Unlock ()
495503 close (done )
496504
497- // Wait for the conversation to stabilize after the error
498- emitter . WaitForStatus ( ctx , t , screentracker . ConversationStatusStable )
505+ // Send should return the error
506+ require . ErrorIs ( t , <- errCh , assert . AnError )
499507
500508 // The partial agent message should be removed on error.
501509 // Only the user message should remain.
@@ -506,28 +514,24 @@ func Test_ErrorRemovesPartialMessage(t *testing.T) {
506514}
507515
508516func Test_LateChunkAfterError_DoesNotCorruptUserMessage (t * testing.T ) {
509- ctx , cancel := context .WithTimeout (context .Background (), 5 * time .Second )
510- defer cancel ()
511-
512517 mClock := quartz .NewMock (t )
513518 mock := newMockAgentIO ()
514- emitter := newMockEmitter ()
515519 started , done := mock .BlockWrite ()
516520
517- conv := acpio .NewACPConversation (ctx , mock , nil , nil , emitter , mClock )
518- conv .Start (ctx )
521+ conv := acpio .NewACPConversation (context . Background () , mock , nil , nil , nil , mClock )
522+ conv .Start (context . Background () )
519523
520524 // Given: a send that fails with an error, removing the agent placeholder
521- err := conv . Send (screentracker. MessagePartText { Content : "hello" } )
522- require . NoError ( t , err )
525+ errCh := make ( chan error , 1 )
526+ go func () { errCh <- conv . Send (screentracker. MessagePartText { Content : "hello" }) }( )
523527 <- started
524528
525529 mock .mu .Lock ()
526530 mock .writeErr = assert .AnError
527531 mock .mu .Unlock ()
528532 close (done )
529533
530- emitter . WaitForStatus ( ctx , t , screentracker . ConversationStatusStable )
534+ require . ErrorIs ( t , <- errCh , assert . AnError )
531535
532536 messages := conv .Messages ()
533537 require .Len (t , messages , 1 , "agent placeholder should be removed after error" )
0 commit comments