@@ -513,3 +513,195 @@ fn register_js_binary_in_nested_object() {
513513
514514 assert_eq ! ( res, r#"{"result":"test-3"}"# ) ;
515515}
516+
517+ // ── Numeric type tests ───────────────────────────────────────────────
518+ // QuickJS stores JSON-parsed numbers as doubles internally. The binary
519+ // host function path (extract_binaries → value_to_json_with_binaries)
520+ // must serialize whole-number floats as integers to preserve serde
521+ // deserialization on the host side.
522+
523+ #[ test]
524+ fn host_fn_with_i32_arg_from_event_data ( ) {
525+ // event.x is parsed from JSON → stored as f64 in QuickJS → must
526+ // arrive at the host as an integer, not 42.0
527+ let handler = Script :: from_content (
528+ r#"
529+ import * as math from "math";
530+ function handler(event) {
531+ return { result: math.double(event.x) };
532+ }
533+ "# ,
534+ ) ;
535+
536+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
537+ proto. register ( "math" , "double" , |x : i32 | x * 2 ) . unwrap ( ) ;
538+
539+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
540+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
541+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
542+
543+ let res = loaded
544+ . handle_event ( "handler" , r#"{"x": 42}"# . to_string ( ) , None )
545+ . unwrap ( ) ;
546+ assert_eq ! ( res, r#"{"result":84}"# ) ;
547+ }
548+
549+ #[ test]
550+ fn host_fn_with_i64_arg_from_event_data ( ) {
551+ let handler = Script :: from_content (
552+ r#"
553+ import * as math from "math";
554+ function handler(event) {
555+ return { result: math.negate(event.x) };
556+ }
557+ "# ,
558+ ) ;
559+
560+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
561+ proto. register ( "math" , "negate" , |x : i64 | -x) . unwrap ( ) ;
562+
563+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
564+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
565+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
566+
567+ let res = loaded
568+ . handle_event ( "handler" , r#"{"x": 100}"# . to_string ( ) , None )
569+ . unwrap ( ) ;
570+ assert_eq ! ( res, r#"{"result":-100}"# ) ;
571+ }
572+
573+ #[ test]
574+ fn host_fn_with_f64_arg_preserves_fractional ( ) {
575+ // Actual floats (3.14) must remain as floats, not be truncated
576+ let handler = Script :: from_content (
577+ r#"
578+ import * as math from "math";
579+ function handler(event) {
580+ return { result: math.half(event.x) };
581+ }
582+ "# ,
583+ ) ;
584+
585+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
586+ proto. register ( "math" , "half" , |x : f64 | x / 2.0 ) . unwrap ( ) ;
587+
588+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
589+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
590+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
591+
592+ let res = loaded
593+ . handle_event ( "handler" , r#"{"x": 3.14}"# . to_string ( ) , None )
594+ . unwrap ( ) ;
595+
596+ let json: serde_json:: Value = serde_json:: from_str ( & res) . unwrap ( ) ;
597+ let result = json[ "result" ] . as_f64 ( ) . unwrap ( ) ;
598+ assert ! (
599+ ( result - 1.57 ) . abs( ) < 0.001 ,
600+ "Expected ~1.57, got {result}"
601+ ) ;
602+ }
603+
604+ #[ test]
605+ fn host_fn_with_bool_arg ( ) {
606+ let handler = Script :: from_content (
607+ r#"
608+ import * as logic from "logic";
609+ function handler(event) {
610+ return { result: logic.flip(event.flag) };
611+ }
612+ "# ,
613+ ) ;
614+
615+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
616+ proto. register ( "logic" , "flip" , |b : bool | !b) . unwrap ( ) ;
617+
618+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
619+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
620+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
621+
622+ let res = loaded
623+ . handle_event ( "handler" , r#"{"flag": true}"# . to_string ( ) , None )
624+ . unwrap ( ) ;
625+ assert_eq ! ( res, r#"{"result":false}"# ) ;
626+ }
627+
628+ #[ test]
629+ fn host_fn_with_mixed_numeric_types ( ) {
630+ // i32 + f64 mix in the same call
631+ let handler = Script :: from_content (
632+ r#"
633+ import * as math from "math";
634+ function handler(event) {
635+ return { result: math.weighted_add(event.a, event.b, event.weight) };
636+ }
637+ "# ,
638+ ) ;
639+
640+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
641+ proto
642+ . register ( "math" , "weighted_add" , |a : i32 , b : i32 , w : f64 | {
643+ ( a as f64 * w + b as f64 * ( 1.0 - w) ) as i32
644+ } )
645+ . unwrap ( ) ;
646+
647+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
648+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
649+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
650+
651+ let res = loaded
652+ . handle_event (
653+ "handler" ,
654+ r#"{"a": 100, "b": 200, "weight": 0.75}"# . to_string ( ) ,
655+ None ,
656+ )
657+ . unwrap ( ) ;
658+ assert_eq ! ( res, r#"{"result":125}"# ) ;
659+ }
660+
661+ #[ test]
662+ fn host_fn_with_negative_integer ( ) {
663+ let handler = Script :: from_content (
664+ r#"
665+ import * as math from "math";
666+ function handler(event) {
667+ return { result: math.abs(event.x) };
668+ }
669+ "# ,
670+ ) ;
671+
672+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
673+ proto. register ( "math" , "abs" , |x : i32 | x. abs ( ) ) . unwrap ( ) ;
674+
675+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
676+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
677+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
678+
679+ let res = loaded
680+ . handle_event ( "handler" , r#"{"x": -42}"# . to_string ( ) , None )
681+ . unwrap ( ) ;
682+ assert_eq ! ( res, r#"{"result":42}"# ) ;
683+ }
684+
685+ #[ test]
686+ fn host_fn_with_zero ( ) {
687+ let handler = Script :: from_content (
688+ r#"
689+ import * as math from "math";
690+ function handler(event) {
691+ return { result: math.inc(event.x) };
692+ }
693+ "# ,
694+ ) ;
695+
696+ let mut proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
697+ proto. register ( "math" , "inc" , |x : i32 | x + 1 ) . unwrap ( ) ;
698+
699+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
700+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
701+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
702+
703+ let res = loaded
704+ . handle_event ( "handler" , r#"{"x": 0}"# . to_string ( ) , None )
705+ . unwrap ( ) ;
706+ assert_eq ! ( res, r#"{"result":1}"# ) ;
707+ }
0 commit comments