@@ -89,6 +89,12 @@ type runtimeStub struct {
8989 checkpointDiffErr error
9090}
9191
92+ type runtimePlanApproverStub struct {
93+ * runtimeStub
94+ approveInput agentruntime.ApproveCurrentPlanInput
95+ approveErr error
96+ }
97+
9298const testBridgeSubjectID = bridgeLocalSubjectID
9399
94100func (s * runtimeStub ) Submit (_ context.Context , input agentruntime.PrepareInput ) error {
@@ -132,6 +138,14 @@ func (s *runtimeStub) ResolvePermission(_ context.Context, input agentruntime.Pe
132138 return s .permissionErr
133139}
134140
141+ func (s * runtimePlanApproverStub ) ApproveCurrentPlan (
142+ _ context.Context ,
143+ input agentruntime.ApproveCurrentPlanInput ,
144+ ) error {
145+ s .approveInput = input
146+ return s .approveErr
147+ }
148+
135149func (s * runtimeStub ) ResolveUserQuestion (_ context.Context , input agentruntime.UserQuestionResolutionInput ) error {
136150 s .userQuestionInput = input
137151 return s .userQuestionErr
@@ -1075,6 +1089,101 @@ func TestGatewayRuntimePortBridgeListSessionTodosAndSnapshot(t *testing.T) {
10751089 })
10761090}
10771091
1092+ func TestGatewayRuntimePortBridgeApprovePlan (t * testing.T ) {
1093+ runtimeSvc := & runtimePlanApproverStub {
1094+ runtimeStub : & runtimeStub {eventsCh : make (chan agentruntime.RuntimeEvent , 1 )},
1095+ }
1096+ bridge , err := newGatewayRuntimePortBridge (context .Background (), runtimeSvc , testSessionStore )
1097+ if err != nil {
1098+ t .Fatalf ("new bridge: %v" , err )
1099+ }
1100+ t .Cleanup (func () { _ = bridge .Close () })
1101+
1102+ result , err := bridge .ApprovePlan (context .Background (), gateway.ApprovePlanInput {
1103+ SubjectID : testBridgeSubjectID ,
1104+ SessionID : " session-1 " ,
1105+ PlanID : " plan-1 " ,
1106+ Revision : 3 ,
1107+ })
1108+ if err != nil {
1109+ t .Fatalf ("approve_plan: %v" , err )
1110+ }
1111+ if runtimeSvc .approveInput .SessionID != "session-1" || runtimeSvc .approveInput .PlanID != "plan-1" || runtimeSvc .approveInput .Revision != 3 {
1112+ t .Fatalf ("approve input = %#v, want trimmed session/plan revision" , runtimeSvc .approveInput )
1113+ }
1114+ if result .PlanID != "plan-1" || result .Revision != 3 || result .Status != "approved" {
1115+ t .Fatalf ("approve result = %#v, want approved plan-1 revision 3" , result )
1116+ }
1117+ }
1118+
1119+ func TestGatewayRuntimePortBridgeApprovePlanUnsupportedRuntime (t * testing.T ) {
1120+ bridge , err := newGatewayRuntimePortBridge (
1121+ context .Background (),
1122+ & runtimeStub {eventsCh : make (chan agentruntime.RuntimeEvent , 1 )},
1123+ testSessionStore ,
1124+ )
1125+ if err != nil {
1126+ t .Fatalf ("new bridge: %v" , err )
1127+ }
1128+ t .Cleanup (func () { _ = bridge .Close () })
1129+
1130+ _ , err = bridge .ApprovePlan (context .Background (), gateway.ApprovePlanInput {
1131+ SubjectID : testBridgeSubjectID ,
1132+ SessionID : "session-1" ,
1133+ PlanID : "plan-1" ,
1134+ Revision : 1 ,
1135+ })
1136+ if err == nil || ! strings .Contains (err .Error (), "runtime does not support plan approval" ) {
1137+ t .Fatalf ("approve_plan unsupported error = %v" , err )
1138+ }
1139+ }
1140+
1141+ func TestGatewayRuntimePortBridgeApprovePlanInvalidAction (t * testing.T ) {
1142+ runtimeSvc := & runtimePlanApproverStub {
1143+ runtimeStub : & runtimeStub {eventsCh : make (chan agentruntime.RuntimeEvent , 1 )},
1144+ approveErr : agentruntime .ErrPlanApprovalRevisionMismatch ,
1145+ }
1146+ bridge , err := newGatewayRuntimePortBridge (context .Background (), runtimeSvc , testSessionStore )
1147+ if err != nil {
1148+ t .Fatalf ("new bridge: %v" , err )
1149+ }
1150+ t .Cleanup (func () { _ = bridge .Close () })
1151+
1152+ _ , err = bridge .ApprovePlan (context .Background (), gateway.ApprovePlanInput {
1153+ SubjectID : testBridgeSubjectID ,
1154+ SessionID : "session-1" ,
1155+ PlanID : "plan-1" ,
1156+ Revision : 1 ,
1157+ })
1158+ if ! errors .Is (err , gateway .ErrRuntimeInvalidAction ) {
1159+ t .Fatalf ("approve_plan error = %v, want ErrRuntimeInvalidAction" , err )
1160+ }
1161+ }
1162+
1163+ func TestGatewayRuntimePortBridgeApprovePlanAccessDenied (t * testing.T ) {
1164+ runtimeSvc := & runtimePlanApproverStub {
1165+ runtimeStub : & runtimeStub {eventsCh : make (chan agentruntime.RuntimeEvent , 1 )},
1166+ }
1167+ bridge , err := newGatewayRuntimePortBridge (context .Background (), runtimeSvc , testSessionStore )
1168+ if err != nil {
1169+ t .Fatalf ("new bridge: %v" , err )
1170+ }
1171+ t .Cleanup (func () { _ = bridge .Close () })
1172+
1173+ _ , err = bridge .ApprovePlan (context .Background (), gateway.ApprovePlanInput {
1174+ SubjectID : "other-subject" ,
1175+ SessionID : "session-1" ,
1176+ PlanID : "plan-1" ,
1177+ Revision : 1 ,
1178+ })
1179+ if ! errors .Is (err , gateway .ErrRuntimeAccessDenied ) {
1180+ t .Fatalf ("approve_plan error = %v, want ErrRuntimeAccessDenied" , err )
1181+ }
1182+ if runtimeSvc .approveInput .SessionID != "" {
1183+ t .Fatalf ("runtime approve should not be called, input = %#v" , runtimeSvc .approveInput )
1184+ }
1185+ }
1186+
10781187func TestGatewayRuntimePortBridgeLoadSessionNotFoundBranches (t * testing.T ) {
10791188 t .Parallel ()
10801189
0 commit comments