@@ -646,3 +646,80 @@ async fn test_cycle_autonomy() {
646646 assert_eq ! ( status, StatusCode :: OK ) ;
647647 assert ! ( json[ "level" ] . is_string( ) ) ;
648648}
649+
650+ /// §5.5 formation self-governance routes — propose-intent enqueues the
651+ /// Fever-gated consensus command; cast_vote validates ids before
652+ /// enqueueing a ballot.
653+ #[ tokio:: test]
654+ async fn test_propose_intent_and_cast_vote_routes ( ) {
655+ let ( router, token) = build_test_app ( true ) ;
656+
657+ // Create a formation to address.
658+ let body = serde_json:: json!( {
659+ "name" : "Governance Squad" ,
660+ "intent" : "Reconnoiter" ,
661+ "guard_mode" : false ,
662+ "agents" : [ {
663+ "connector_name" : "connector-test" ,
664+ "trigger_name" : "event_received" ,
665+ "action_connector" : "connector-test" ,
666+ "action_name" : "send_message"
667+ } ]
668+ } ) ;
669+ let req = Request :: post ( "/formations/deploy-team" )
670+ . header ( "Authorization" , format ! ( "Bearer {token}" ) )
671+ . header ( "Content-Type" , "application/json" )
672+ . body ( Body :: from ( serde_json:: to_vec ( & body) . unwrap ( ) ) )
673+ . unwrap ( ) ;
674+ let ( status, json) = send ( router. clone ( ) , req) . await ;
675+ assert_eq ! ( status, StatusCode :: CREATED ) ;
676+ let fid = json[ "formation_id" ] . as_str ( ) . unwrap ( ) . to_owned ( ) ;
677+
678+ // Propose an intent change — the route enqueues the consensus
679+ // command (the Fever gate is enforced by the bot event loop).
680+ let req = Request :: post ( format ! ( "/formations/{fid}/propose-intent" ) )
681+ . header ( "Authorization" , format ! ( "Bearer {token}" ) )
682+ . header ( "Content-Type" , "application/json" )
683+ . body ( Body :: from (
684+ serde_json:: to_vec ( & serde_json:: json!( { "intent" : "Execute" } ) ) . unwrap ( ) ,
685+ ) )
686+ . unwrap ( ) ;
687+ let ( status, json) = send ( router. clone ( ) , req) . await ;
688+ assert_eq ! ( status, StatusCode :: OK ) ;
689+ assert_eq ! ( json[ "proposed" ] , fid) ;
690+
691+ // Missing intent body → 400.
692+ let req = Request :: post ( format ! ( "/formations/{fid}/propose-intent" ) )
693+ . header ( "Authorization" , format ! ( "Bearer {token}" ) )
694+ . header ( "Content-Type" , "application/json" )
695+ . body ( Body :: from ( b"{}" . to_vec ( ) ) )
696+ . unwrap ( ) ;
697+ let ( status, _) = send ( router. clone ( ) , req) . await ;
698+ assert_eq ! ( status, StatusCode :: BAD_REQUEST ) ;
699+
700+ // Cast a ballot with well-formed ids → enqueued (200).
701+ let vote_id = uuid:: Uuid :: new_v4 ( ) ;
702+ let voter = uuid:: Uuid :: new_v4 ( ) ;
703+ let req = Request :: post ( format ! ( "/formations/{fid}/votes/{vote_id}" ) )
704+ . header ( "Authorization" , format ! ( "Bearer {token}" ) )
705+ . header ( "Content-Type" , "application/json" )
706+ . body ( Body :: from (
707+ serde_json:: to_vec ( & serde_json:: json!( { "voter" : voter, "approve" : true } ) ) . unwrap ( ) ,
708+ ) )
709+ . unwrap ( ) ;
710+ let ( status, json) = send ( router. clone ( ) , req) . await ;
711+ assert_eq ! ( status, StatusCode :: OK ) ;
712+ assert_eq ! ( json[ "voted" ] , vote_id. to_string( ) ) ;
713+
714+ // Malformed voter uuid → 400 from the runtime op's validation.
715+ let req = Request :: post ( format ! ( "/formations/{fid}/votes/{vote_id}" ) )
716+ . header ( "Authorization" , format ! ( "Bearer {token}" ) )
717+ . header ( "Content-Type" , "application/json" )
718+ . body ( Body :: from (
719+ serde_json:: to_vec ( & serde_json:: json!( { "voter" : "not-a-uuid" , "approve" : true } ) )
720+ . unwrap ( ) ,
721+ ) )
722+ . unwrap ( ) ;
723+ let ( status, _) = send ( router, req) . await ;
724+ assert_eq ! ( status, StatusCode :: BAD_REQUEST ) ;
725+ }
0 commit comments