@@ -404,3 +404,240 @@ pub fn py_to_invocation_response(
404404 result : py_to_status_result ( get_child ( d, "result" ) ) ,
405405 } )
406406}
407+
408+ #[ cfg( test) ]
409+ mod tests {
410+ use super :: * ;
411+ use crate :: pb:: messages:: {
412+ parameter_binding, streaming_message:: Content , BindingInfo , FunctionLoadRequest ,
413+ InvocationRequest , ParameterBinding , RpcFunctionMetadata , TypedData , WorkerInitRequest ,
414+ } ;
415+ use crate :: pb:: messages:: { streaming_message, typed_data} ;
416+ use pyo3:: types:: { PyDict , PyList , PyString , PyTuple } ;
417+
418+ fn datum_tuple < ' py > ( py : Python < ' py > , kind : & str , value : & str ) -> Bound < ' py , PyAny > {
419+ PyTuple :: new (
420+ py,
421+ vec ! [
422+ PyString :: new( py, kind) . into_any( ) ,
423+ PyString :: new( py, value) . into_any( ) ,
424+ ] ,
425+ )
426+ . unwrap ( )
427+ . into_any ( )
428+ }
429+
430+ // --- pure (no interpreter) -------------------------------------------
431+ #[ test]
432+ fn start_stream_carries_worker_id_and_empty_request_id ( ) {
433+ let msg = start_stream_message ( "worker-123" ) ;
434+ assert_eq ! ( msg. request_id, "" ) ;
435+ match msg. content {
436+ Some ( Content :: StartStream ( s) ) => assert_eq ! ( s. worker_id, "worker-123" ) ,
437+ other => panic ! ( "expected StartStream, got {other:?}" ) ,
438+ }
439+ }
440+
441+ // --- inbound request (prost -> dict) ---------------------------------
442+ #[ test]
443+ fn worker_init_request_decodes_to_dict ( ) {
444+ Python :: attach ( |py| {
445+ let mut caps = std:: collections:: HashMap :: new ( ) ;
446+ caps. insert ( "RawHttpBodyBytes" . to_string ( ) , "true" . to_string ( ) ) ;
447+ let r = WorkerInitRequest {
448+ function_app_directory : "/home/site/wwwroot" . into ( ) ,
449+ host_version : "4.1.0" . into ( ) ,
450+ capabilities : caps,
451+ ..Default :: default ( )
452+ } ;
453+ let d = worker_init_req_to_py ( py, & r) . unwrap ( ) ;
454+ let dir: String = d
455+ . get_item ( "function_app_directory" )
456+ . unwrap ( )
457+ . unwrap ( )
458+ . extract ( )
459+ . unwrap ( ) ;
460+ assert_eq ! ( dir, "/home/site/wwwroot" ) ;
461+ let hv: String = d
462+ . get_item ( "host_version" )
463+ . unwrap ( )
464+ . unwrap ( )
465+ . extract ( )
466+ . unwrap ( ) ;
467+ assert_eq ! ( hv, "4.1.0" ) ;
468+ } ) ;
469+ }
470+
471+ #[ test]
472+ fn function_load_request_decodes_nested_bindings ( ) {
473+ Python :: attach ( |py| {
474+ let mut bindings = std:: collections:: HashMap :: new ( ) ;
475+ bindings. insert (
476+ "req" . to_string ( ) ,
477+ BindingInfo {
478+ r#type : "httpTrigger" . into ( ) ,
479+ direction : 0 ,
480+ data_type : 0 ,
481+ ..Default :: default ( )
482+ } ,
483+ ) ;
484+ let r = FunctionLoadRequest {
485+ function_id : "fid-1" . into ( ) ,
486+ metadata : Some ( RpcFunctionMetadata {
487+ name : "hello" . into ( ) ,
488+ entry_point : "main" . into ( ) ,
489+ bindings,
490+ ..Default :: default ( )
491+ } ) ,
492+ ..Default :: default ( )
493+ } ;
494+ let d = function_load_req_to_py ( py, & r) . unwrap ( ) ;
495+ let fid: String = d
496+ . get_item ( "function_id" )
497+ . unwrap ( )
498+ . unwrap ( )
499+ . extract ( )
500+ . unwrap ( ) ;
501+ assert_eq ! ( fid, "fid-1" ) ;
502+ let meta = d. get_item ( "metadata" ) . unwrap ( ) . unwrap ( ) ;
503+ let name: String = meta. get_item ( "name" ) . unwrap ( ) . extract ( ) . unwrap ( ) ;
504+ assert_eq ! ( name, "hello" ) ;
505+ let btype: String = meta
506+ . get_item ( "bindings" )
507+ . unwrap ( )
508+ . get_item ( "req" )
509+ . unwrap ( )
510+ . get_item ( "type" )
511+ . unwrap ( )
512+ . extract ( )
513+ . unwrap ( ) ;
514+ assert_eq ! ( btype, "httpTrigger" ) ;
515+ } ) ;
516+ }
517+
518+ #[ test]
519+ fn invocation_request_decodes_inputs_and_metadata ( ) {
520+ Python :: attach ( |py| {
521+ let mut trigger_metadata = std:: collections:: HashMap :: new ( ) ;
522+ trigger_metadata. insert (
523+ "Query" . to_string ( ) ,
524+ TypedData {
525+ data : Some ( typed_data:: Data :: String ( "q" . into ( ) ) ) ,
526+ } ,
527+ ) ;
528+ let r = InvocationRequest {
529+ invocation_id : "inv-1" . into ( ) ,
530+ function_id : "fid-1" . into ( ) ,
531+ input_data : vec ! [ ParameterBinding {
532+ name: "req" . into( ) ,
533+ rpc_data: Some ( parameter_binding:: RpcData :: Data ( TypedData {
534+ data: Some ( typed_data:: Data :: String ( "body" . into( ) ) ) ,
535+ } ) ) ,
536+ } ] ,
537+ trigger_metadata,
538+ ..Default :: default ( )
539+ } ;
540+ let d = invocation_request_to_py ( py, & r) . unwrap ( ) ;
541+ let inv: String = d
542+ . get_item ( "invocation_id" )
543+ . unwrap ( )
544+ . unwrap ( )
545+ . extract ( )
546+ . unwrap ( ) ;
547+ assert_eq ! ( inv, "inv-1" ) ;
548+ let inputs = d. get_item ( "input_data" ) . unwrap ( ) . unwrap ( ) ;
549+ let first = inputs. get_item ( 0 ) . unwrap ( ) ;
550+ let name: String = first. get_item ( 0 ) . unwrap ( ) . extract ( ) . unwrap ( ) ;
551+ assert_eq ! ( name, "req" ) ;
552+ let datum_kind: String = first
553+ . get_item ( 1 )
554+ . unwrap ( )
555+ . get_item ( 0 )
556+ . unwrap ( )
557+ . extract ( )
558+ . unwrap ( ) ;
559+ assert_eq ! ( datum_kind, "string" ) ;
560+ } ) ;
561+ }
562+
563+ // --- adapter response dict (dict -> prost) ---------------------------
564+ #[ test]
565+ fn worker_init_response_maps_capabilities_and_status ( ) {
566+ Python :: attach ( |py| {
567+ let d = PyDict :: new ( py) ;
568+ let caps = PyDict :: new ( py) ;
569+ caps. set_item ( "WorkerStatus" , "true" ) . unwrap ( ) ;
570+ d. set_item ( "capabilities" , caps) . unwrap ( ) ;
571+ let result = PyDict :: new ( py) ;
572+ result. set_item ( "status" , 1 ) . unwrap ( ) ;
573+ d. set_item ( "result" , result) . unwrap ( ) ;
574+
575+ let resp = py_to_worker_init_response ( & d. into_any ( ) ) . unwrap ( ) ;
576+ assert_eq ! (
577+ resp. capabilities. get( "WorkerStatus" ) . map( String :: as_str) ,
578+ Some ( "true" )
579+ ) ;
580+ assert_eq ! ( resp. result. unwrap( ) . status, 1 ) ;
581+ } ) ;
582+ }
583+
584+ #[ test]
585+ fn function_load_response_maps_id_and_status ( ) {
586+ Python :: attach ( |py| {
587+ let d = PyDict :: new ( py) ;
588+ d. set_item ( "function_id" , "fid-1" ) . unwrap ( ) ;
589+ let result = PyDict :: new ( py) ;
590+ result. set_item ( "status" , 1 ) . unwrap ( ) ;
591+ d. set_item ( "result" , result) . unwrap ( ) ;
592+
593+ let resp = py_to_function_load_response ( & d. into_any ( ) ) . unwrap ( ) ;
594+ assert_eq ! ( resp. function_id, "fid-1" ) ;
595+ assert_eq ! ( resp. result. unwrap( ) . status, 1 ) ;
596+ } ) ;
597+ }
598+
599+ #[ test]
600+ fn invocation_response_maps_return_value_and_result ( ) {
601+ Python :: attach ( |py| {
602+ let d = PyDict :: new ( py) ;
603+ d. set_item ( "return_value" , datum_tuple ( py, "string" , "ok" ) )
604+ . unwrap ( ) ;
605+ d. set_item ( "output_data" , PyList :: empty ( py) ) . unwrap ( ) ;
606+ let result = PyDict :: new ( py) ;
607+ result. set_item ( "status" , 1 ) . unwrap ( ) ;
608+ d. set_item ( "result" , result) . unwrap ( ) ;
609+
610+ let resp = py_to_invocation_response ( py, & d. into_any ( ) , "inv-1" ) . unwrap ( ) ;
611+ assert_eq ! ( resp. invocation_id, "inv-1" ) ;
612+ assert_eq ! ( resp. result. unwrap( ) . status, 1 ) ;
613+ match resp. return_value . and_then ( |t| t. data ) {
614+ Some ( typed_data:: Data :: String ( s) ) => assert_eq ! ( s, "ok" ) ,
615+ other => panic ! ( "unexpected return_value: {other:?}" ) ,
616+ }
617+ } ) ;
618+ }
619+
620+ // --- logging ----------------------------------------------------------
621+ #[ test]
622+ fn log_dict_builds_rpc_log_streaming_message ( ) {
623+ Python :: attach ( |py| {
624+ let d = PyDict :: new ( py) ;
625+ d. set_item ( "invocation_id" , "inv-1" ) . unwrap ( ) ;
626+ d. set_item ( "category" , "worker" ) . unwrap ( ) ;
627+ d. set_item ( "level" , 4 ) . unwrap ( ) ;
628+ d. set_item ( "message" , "hello logs" ) . unwrap ( ) ;
629+
630+ let msg = py_log_to_streaming_message ( & d. into_any ( ) , "req-9" ) . unwrap ( ) ;
631+ assert_eq ! ( msg. request_id, "req-9" ) ;
632+ match msg. content {
633+ Some ( streaming_message:: Content :: RpcLog ( l) ) => {
634+ assert_eq ! ( l. invocation_id, "inv-1" ) ;
635+ assert_eq ! ( l. category, "worker" ) ;
636+ assert_eq ! ( l. level, 4 ) ;
637+ assert_eq ! ( l. message, "hello logs" ) ;
638+ }
639+ other => panic ! ( "expected RpcLog, got {other:?}" ) ,
640+ }
641+ } ) ;
642+ }
643+ }
0 commit comments