@@ -160,4 +160,168 @@ mod tests {
160160 assert_eq ! ( message, LifespanSendMessage :: LifespanStartupComplete ) ;
161161 } ) ;
162162 }
163+
164+ #[ test]
165+ fn test_lifespan_send_message_from_pyobject_error_cases ( ) {
166+ Python :: with_gil ( |py| {
167+ // Test missing 'type' key
168+ let dict = PyDict :: new ( py) ;
169+ let result: Result < LifespanSendMessage , _ > = dict. extract ( ) ;
170+ assert ! ( result. is_err( ) ) ;
171+ assert ! (
172+ result
173+ . unwrap_err( )
174+ . to_string( )
175+ . contains( "Missing 'type' key" )
176+ ) ;
177+
178+ // Test unknown message type
179+ let dict = PyDict :: new ( py) ;
180+ dict. set_item ( "type" , "unknown.message.type" ) . unwrap ( ) ;
181+ let result: Result < LifespanSendMessage , _ > = dict. extract ( ) ;
182+ assert ! ( result. is_err( ) ) ;
183+ assert ! (
184+ result
185+ . unwrap_err( )
186+ . to_string( )
187+ . contains( "Unknown Lifespan send message type" )
188+ ) ;
189+
190+ // Test non-dict object
191+ let list = py. eval ( c"[]" , None , None ) . unwrap ( ) ;
192+ let result: Result < LifespanSendMessage , _ > = list. extract ( ) ;
193+ assert ! ( result. is_err( ) ) ;
194+
195+ // Test invalid type value (not string)
196+ let dict = PyDict :: new ( py) ;
197+ dict. set_item ( "type" , 123 ) . unwrap ( ) ;
198+ let result: Result < LifespanSendMessage , _ > = dict. extract ( ) ;
199+ assert ! ( result. is_err( ) ) ;
200+ } ) ;
201+ }
202+
203+ #[ test]
204+ fn test_lifespan_send_message_traits ( ) {
205+ // Test Debug trait
206+ let msg1 = LifespanSendMessage :: LifespanStartupComplete ;
207+ let msg2 = LifespanSendMessage :: LifespanShutdownComplete ;
208+
209+ let debug1 = format ! ( "{:?}" , msg1) ;
210+ let debug2 = format ! ( "{:?}" , msg2) ;
211+ assert ! ( debug1. contains( "LifespanStartupComplete" ) ) ;
212+ assert ! ( debug2. contains( "LifespanShutdownComplete" ) ) ;
213+
214+ // Test Clone
215+ let cloned1 = msg1. clone ( ) ;
216+ let cloned2 = msg2. clone ( ) ;
217+
218+ // Test PartialEq and Eq
219+ assert_eq ! ( msg1, cloned1) ;
220+ assert_eq ! ( msg2, cloned2) ;
221+ assert_ne ! ( msg1, msg2) ;
222+
223+ // Test Hash
224+ use std:: collections:: HashSet ;
225+ let mut set = HashSet :: new ( ) ;
226+ set. insert ( msg1) ;
227+ set. insert ( cloned1) ; // Should not increase size due to equality
228+ set. insert ( msg2) ;
229+ assert_eq ! ( set. len( ) , 2 ) ; // Only unique messages
230+ }
231+
232+ #[ test]
233+ fn test_lifespan_receive_message_traits ( ) {
234+ // Test all the derive traits for LifespanReceiveMessage
235+ let msg1 = LifespanReceiveMessage :: LifespanStartup ;
236+ let msg2 = LifespanReceiveMessage :: LifespanShutdown ;
237+
238+ // Test Debug
239+ let debug1 = format ! ( "{:?}" , msg1) ;
240+ let debug2 = format ! ( "{:?}" , msg2) ;
241+ assert ! ( debug1. contains( "LifespanStartup" ) ) ;
242+ assert ! ( debug2. contains( "LifespanShutdown" ) ) ;
243+
244+ // Test Clone and Copy
245+ let cloned1 = msg1. clone ( ) ;
246+ let copied1 = msg1;
247+
248+ // Test PartialEq and Eq
249+ assert_eq ! ( msg1, cloned1) ;
250+ assert_eq ! ( msg1, copied1) ;
251+ assert_ne ! ( msg1, msg2) ;
252+
253+ // Test Hash
254+ use std:: collections:: HashSet ;
255+ let mut set = HashSet :: new ( ) ;
256+ set. insert ( msg1) ;
257+ set. insert ( copied1) ; // Should not increase size due to equality
258+ set. insert ( msg2) ;
259+ assert_eq ! ( set. len( ) , 2 ) ; // Only unique messages
260+ }
261+
262+ #[ test]
263+ fn test_lifespan_scope_with_populated_state ( ) {
264+ Python :: with_gil ( |py| {
265+ // Create a state dictionary with some data
266+ let state_dict = PyDict :: new ( py) ;
267+ state_dict. set_item ( "initialized" , true ) . unwrap ( ) ;
268+ state_dict. set_item ( "counter" , 42 ) . unwrap ( ) ;
269+
270+ let lifespan_scope = LifespanScope {
271+ state : Some ( state_dict. unbind ( ) ) ,
272+ } ;
273+
274+ let py_obj = lifespan_scope. into_pyobject ( py) . unwrap ( ) ;
275+
276+ // Verify the scope structure
277+ assert_eq ! (
278+ dict_extract!( py_obj, "type" , String ) ,
279+ "lifespan" . to_string( )
280+ ) ;
281+
282+ // Verify ASGI info is present
283+ let asgi_info = dict_get ! ( py_obj, "asgi" ) ;
284+ let asgi_dict = asgi_info. downcast :: < PyDict > ( ) . unwrap ( ) ;
285+ assert_eq ! (
286+ asgi_dict
287+ . get_item( "version" )
288+ . unwrap( )
289+ . unwrap( )
290+ . extract:: <String >( )
291+ . unwrap( ) ,
292+ "3.0"
293+ ) ;
294+ assert_eq ! (
295+ asgi_dict
296+ . get_item( "spec_version" )
297+ . unwrap( )
298+ . unwrap( )
299+ . extract:: <String >( )
300+ . unwrap( ) ,
301+ "2.0"
302+ ) ;
303+
304+ // Verify state is preserved
305+ let state_obj = dict_get ! ( py_obj, "state" ) ;
306+ let state_dict = state_obj. downcast :: < PyDict > ( ) . unwrap ( ) ;
307+ assert_eq ! (
308+ state_dict
309+ . get_item( "initialized" )
310+ . unwrap( )
311+ . unwrap( )
312+ . extract:: <bool >( )
313+ . unwrap( ) ,
314+ true
315+ ) ;
316+ assert_eq ! (
317+ state_dict
318+ . get_item( "counter" )
319+ . unwrap( )
320+ . unwrap( )
321+ . extract:: <i32 >( )
322+ . unwrap( ) ,
323+ 42
324+ ) ;
325+ } ) ;
326+ }
163327}
0 commit comments