@@ -8,7 +8,9 @@ import kotlin.test.assertEquals
88import kotlin.test.assertNotNull
99import kotlin.test.assertTrue
1010import kotlinx.coroutines.ExperimentalCoroutinesApi
11+ import kotlinx.coroutines.cancelAndJoin
1112import kotlinx.coroutines.delay
13+ import kotlinx.coroutines.launch
1214import kotlinx.coroutines.runBlocking
1315import kotlinx.coroutines.test.TestScope
1416import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -102,42 +104,50 @@ class AgentMessageApiTest {
102104 assertEquals(2 , fetchedThread2.messages.size)
103105
104106 // Escalate -> WAITING_FOR_HUMAN
105- api.escalateToHuman(
106- threadId = thread.id,
107- reason = " Need approval" ,
108- )
109- val fetchedThread3 = api.getThread(thread.id).getOrNull()
110- assertNotNull(fetchedThread3)
111- assertEquals(EventStatus .WaitingForHuman , fetchedThread3.status)
112-
113- // Posting now should fail, since the thread is waiting for human
114- var threw = false
115- try {
116- api.postMessage(
107+ val escalationJob = launch {
108+ api.escalateToHuman(
117109 threadId = thread.id,
118- content = " Should fail " ,
110+ reason = " Need approval " ,
119111 )
120- } catch (e: IllegalArgumentException ) {
121- threw = true
122- } catch (e: IllegalStateException ) {
123- threw = true
124112 }
125- assertTrue(threw)
126-
127- // Resolve
128- api.resolveThread(thread.id)
129- val fetched4 = api.getThread(thread.id).getOrNull()
130- assertNotNull(fetched4)
131- assertEquals(EventStatus .Resolved , fetched4.status)
132-
133- // allow async event handlers to run
134- delay(200 )
135113
136- // Events were published (at least 1 create, 2 posts, 1 escalation, 2 status changes)
137- assertTrue(received.any { it is MessageEvent .ThreadCreated })
138- assertTrue(received.count { it is MessageEvent .MessagePosted } >= 2 )
139- assertTrue(received.any { it is MessageEvent .EscalationRequested })
140- assertTrue(received.count { it is MessageEvent .ThreadStatusChanged } >= 2 )
114+ try {
115+ delay(200 )
116+ val fetchedThread3 = api.getThread(thread.id).getOrNull()
117+ assertNotNull(fetchedThread3)
118+ assertEquals(EventStatus .WaitingForHuman , fetchedThread3.status)
119+
120+ // Posting now should fail, since the thread is waiting for human
121+ var threw = false
122+ try {
123+ api.postMessage(
124+ threadId = thread.id,
125+ content = " Should fail" ,
126+ )
127+ } catch (e: IllegalArgumentException ) {
128+ threw = true
129+ } catch (e: IllegalStateException ) {
130+ threw = true
131+ }
132+ assertTrue(threw)
133+
134+ // Resolve
135+ api.resolveThread(thread.id)
136+ val fetched4 = api.getThread(thread.id).getOrNull()
137+ assertNotNull(fetched4)
138+ assertEquals(EventStatus .Resolved , fetched4.status)
139+
140+ // allow async event handlers to run
141+ delay(200 )
142+
143+ // Events were published (at least 1 create, 2 posts, 1 escalation, 2 status changes)
144+ assertTrue(received.any { it is MessageEvent .ThreadCreated })
145+ assertTrue(received.count { it is MessageEvent .MessagePosted } >= 2 )
146+ assertTrue(received.any { it is MessageEvent .EscalationRequested })
147+ assertTrue(received.count { it is MessageEvent .ThreadStatusChanged } >= 2 )
148+ } finally {
149+ escalationJob.cancelAndJoin()
150+ }
141151
142152 // ** TODO: Test subscriptions can be unsubscribed from. */
143153 }
@@ -166,48 +176,56 @@ class AgentMessageApiTest {
166176 assertEquals(EventStatus .Open , fetchedThread1.status)
167177
168178 // Escalate to WAITING_FOR_HUMAN
169- api.escalateToHuman(
170- threadId = thread.id,
171- reason = " Need human input" ,
172- )
173-
174- val fetchedThread2 = api.getThread(thread.id).getOrNull()
175- assertNotNull(fetchedThread2)
176- assertEquals(EventStatus .WaitingForHuman , fetchedThread2.status)
177-
178- // Verify posting is blocked
179- var blocked = false
180- try {
181- api.postMessage(thread.id, " Should fail" )
182- } catch (e: IllegalArgumentException ) {
183- blocked = true
179+ val escalationJob = launch {
180+ api.escalateToHuman(
181+ threadId = thread.id,
182+ reason = " Need human input" ,
183+ )
184184 }
185- assertTrue(blocked, " Posting should be blocked when waiting for human" )
186-
187- // Reopen the thread
188- api.reopenThread(thread.id)
189-
190- val fetchedThread3 = api.getThread(thread.id).getOrNull()
191- assertNotNull(fetchedThread3)
192- assertEquals(EventStatus .Open , fetchedThread3.status)
193-
194- // Now posting should succeed
195- val newMessage = api.postMessage(thread.id, " Human intervention complete" )
196- assertEquals(" Human intervention complete" , newMessage.content)
197185
198- // Verify thread has the new message
199- val fetchedThread4 = api.getThread(thread.id).getOrNull()
200- assertNotNull(fetchedThread4)
201- assertEquals(2 , fetchedThread4.messages.size)
202-
203- // Allow async handlers to run
204- delay(200 )
186+ try {
187+ delay(200 )
188+
189+ val fetchedThread2 = api.getThread(thread.id).getOrNull()
190+ assertNotNull(fetchedThread2)
191+ assertEquals(EventStatus .WaitingForHuman , fetchedThread2.status)
192+
193+ // Verify posting is blocked
194+ var blocked = false
195+ try {
196+ api.postMessage(thread.id, " Should fail" )
197+ } catch (e: IllegalArgumentException ) {
198+ blocked = true
199+ }
200+ assertTrue(blocked, " Posting should be blocked when waiting for human" )
201+
202+ // Reopen the thread
203+ api.reopenThread(thread.id)
205204
206- // Verify status change events
207- val statusChanges = received.filterIsInstance<MessageEvent .ThreadStatusChanged >()
208- assertTrue(statusChanges.size >= 2 )
209- assertTrue(statusChanges.any { it.newStatus == EventStatus .WaitingForHuman })
210- assertTrue(statusChanges.any { it.newStatus == EventStatus .Open })
205+ val fetchedThread3 = api.getThread(thread.id).getOrNull()
206+ assertNotNull(fetchedThread3)
207+ assertEquals(EventStatus .Open , fetchedThread3.status)
208+
209+ // Now posting should succeed
210+ val newMessage = api.postMessage(thread.id, " Human intervention complete" )
211+ assertEquals(" Human intervention complete" , newMessage.content)
212+
213+ // Verify thread has the new message
214+ val fetchedThread4 = api.getThread(thread.id).getOrNull()
215+ assertNotNull(fetchedThread4)
216+ assertEquals(2 , fetchedThread4.messages.size)
217+
218+ // Allow async handlers to run
219+ delay(200 )
220+
221+ // Verify status change events
222+ val statusChanges = received.filterIsInstance<MessageEvent .ThreadStatusChanged >()
223+ assertTrue(statusChanges.size >= 2 )
224+ assertTrue(statusChanges.any { it.newStatus == EventStatus .WaitingForHuman })
225+ assertTrue(statusChanges.any { it.newStatus == EventStatus .Open })
226+ } finally {
227+ escalationJob.cancelAndJoin()
228+ }
211229 }
212230 }
213231
@@ -227,19 +245,25 @@ class AgentMessageApiTest {
227245 initialMessageContent = " Need a decision" ,
228246 )
229247
230- api.escalateToHuman(
231- threadId = thread.id,
232- reason = " Need human input on release timing " ,
233- context = mapOf ( " release " to " v1 " ) ,
234- )
235-
236- delay( 200 )
248+ val escalationJob = launch {
249+ api.escalateToHuman(
250+ threadId = thread.id ,
251+ reason = " Need human input on release timing " ,
252+ context = mapOf ( " release " to " v1 " ),
253+ )
254+ }
237255
238- assertEquals(1 , received.size)
239- val event = received.single()
240- assertEquals(thread.id, event.threadId)
241- assertEquals(" Need human input on release timing" , event.reason)
242- assertEquals(mapOf (" release" to " v1" ), event.context)
256+ try {
257+ delay(200 )
258+
259+ assertEquals(1 , received.size)
260+ val event = received.single()
261+ assertEquals(thread.id, event.threadId)
262+ assertEquals(" Need human input on release timing" , event.reason)
263+ assertEquals(mapOf (" release" to " v1" ), event.context)
264+ } finally {
265+ escalationJob.cancelAndJoin()
266+ }
243267 }
244268 }
245269
0 commit comments