@@ -119,3 +119,132 @@ func TestSession_On(t *testing.T) {
119119 }
120120 })
121121}
122+
123+ func TestSession_Shutdown (t * testing.T ) {
124+ t .Run ("shutdown event dispatches to handlers" , func (t * testing.T ) {
125+ session := & Session {
126+ handlers : make ([]sessionHandler , 0 ),
127+ }
128+
129+ var received []SessionEvent
130+ session .On (func (event SessionEvent ) {
131+ received = append (received , event )
132+ })
133+
134+ session .dispatchEvent (SessionEvent {Type : "session.shutdown" })
135+
136+ if len (received ) != 1 {
137+ t .Fatalf ("Expected 1 event, got %d" , len (received ))
138+ }
139+ if received [0 ].Type != "session.shutdown" {
140+ t .Errorf ("Expected session.shutdown event, got %s" , received [0 ].Type )
141+ }
142+ })
143+
144+ t .Run ("handlers still active after shutdown flag set" , func (t * testing.T ) {
145+ session := & Session {
146+ handlers : make ([]sessionHandler , 0 ),
147+ }
148+
149+ var received []SessionEvent
150+ session .On (func (event SessionEvent ) {
151+ received = append (received , event )
152+ })
153+
154+ // Simulate what Shutdown() does: set the flag
155+ session .isShutdown .Store (true )
156+
157+ // Handlers should still be active — Shutdown does not clear them
158+ session .dispatchEvent (SessionEvent {Type : "session.shutdown" })
159+
160+ if len (received ) != 1 {
161+ t .Fatalf ("Expected 1 event after shutdown, got %d" , len (received ))
162+ }
163+ if received [0 ].Type != "session.shutdown" {
164+ t .Errorf ("Expected session.shutdown, got %s" , received [0 ].Type )
165+ }
166+ })
167+
168+ t .Run ("shutdown idempotency via atomic flag" , func (t * testing.T ) {
169+ session := & Session {
170+ handlers : make ([]sessionHandler , 0 ),
171+ }
172+
173+ // First swap should return false (was not shut down)
174+ if session .isShutdown .Swap (true ) {
175+ t .Error ("Expected first Swap to return false" )
176+ }
177+
178+ // Second swap should return true (already shut down)
179+ if ! session .isShutdown .Swap (true ) {
180+ t .Error ("Expected second Swap to return true" )
181+ }
182+ })
183+
184+ t .Run ("disconnect clears handlers" , func (t * testing.T ) {
185+ session := & Session {
186+ handlers : make ([]sessionHandler , 0 ),
187+ toolHandlers : make (map [string ]ToolHandler ),
188+ }
189+
190+ var count int
191+ session .On (func (event SessionEvent ) { count ++ })
192+
193+ // Dispatch before disconnect — handler should fire
194+ session .dispatchEvent (SessionEvent {Type : "test" })
195+ if count != 1 {
196+ t .Fatalf ("Expected 1 event before disconnect, got %d" , count )
197+ }
198+
199+ // Simulate Disconnect's handler-clearing logic
200+ session .handlerMutex .Lock ()
201+ session .handlers = nil
202+ session .handlerMutex .Unlock ()
203+
204+ session .toolHandlersM .Lock ()
205+ session .toolHandlers = nil
206+ session .toolHandlersM .Unlock ()
207+
208+ session .permissionMux .Lock ()
209+ session .permissionHandler = nil
210+ session .permissionMux .Unlock ()
211+
212+ // Dispatch after disconnect — handler should NOT fire
213+ session .dispatchEvent (SessionEvent {Type : "test" })
214+ if count != 1 {
215+ t .Errorf ("Expected no additional events after disconnect, got %d total" , count )
216+ }
217+ })
218+
219+ t .Run ("two-phase shutdown then disconnect preserves notification" , func (t * testing.T ) {
220+ session := & Session {
221+ handlers : make ([]sessionHandler , 0 ),
222+ }
223+
224+ var events []string
225+ session .On (func (event SessionEvent ) {
226+ events = append (events , string (event .Type ))
227+ })
228+
229+ // Phase 1: Shutdown sends the RPC (simulated) — handlers still active
230+ session .isShutdown .Store (true )
231+
232+ // Server sends back shutdown notification — handler receives it
233+ session .dispatchEvent (SessionEvent {Type : "session.shutdown" })
234+
235+ // Phase 2: Clear handlers (simulating Disconnect)
236+ session .handlerMutex .Lock ()
237+ session .handlers = nil
238+ session .handlerMutex .Unlock ()
239+
240+ // Any further events should not reach handlers
241+ session .dispatchEvent (SessionEvent {Type : "should.not.arrive" })
242+
243+ if len (events ) != 1 {
244+ t .Fatalf ("Expected exactly 1 event, got %d: %v" , len (events ), events )
245+ }
246+ if events [0 ] != "session.shutdown" {
247+ t .Errorf ("Expected session.shutdown, got %s" , events [0 ])
248+ }
249+ })
250+ }
0 commit comments