Skip to content

Commit 216fac0

Browse files
committed
test(gateway): 补齐计划审批分支覆盖
覆盖 approve_plan 的网关校验、运行时端口缺失、多工作区路由错误和 bridge 访问拒绝分支。
1 parent 9bbddda commit 216fac0

4 files changed

Lines changed: 216 additions & 0 deletions

File tree

internal/cli/gateway_runtime_bridge_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,30 @@ func TestGatewayRuntimePortBridgeApprovePlanInvalidAction(t *testing.T) {
11601160
}
11611161
}
11621162

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+
11631187
func TestGatewayRuntimePortBridgeLoadSessionNotFoundBranches(t *testing.T) {
11641188
t.Parallel()
11651189

internal/gateway/bootstrap_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ type bootstrapRuntimeStub struct {
5959
checkpointDiffFn func(ctx context.Context, input CheckpointDiffInput) (CheckpointDiffResult, error)
6060
}
6161

62+
type runtimePortWithoutPlanApproval struct {
63+
RuntimePort
64+
}
65+
6266
func (s *bootstrapRuntimeStub) Run(ctx context.Context, input RunInput) error {
6367
if s != nil && s.runFn != nil {
6468
return s.runFn(ctx, input)
@@ -2630,6 +2634,86 @@ func TestHandleCancelListLoadResolveBranches(t *testing.T) {
26302634
}
26312635
})
26322636

2637+
t.Run("approve plan runtime unavailable", func(t *testing.T) {
2638+
response := handleApprovePlanFrame(context.Background(), MessageFrame{
2639+
Type: FrameTypeRequest,
2640+
Action: FrameActionApprovePlan,
2641+
Payload: map[string]any{
2642+
"session_id": "session-1",
2643+
"plan_id": "plan-1",
2644+
"revision": 1,
2645+
},
2646+
}, nil)
2647+
if response.Type != FrameTypeError {
2648+
t.Fatalf("response type = %q, want %q", response.Type, FrameTypeError)
2649+
}
2650+
if response.Error == nil || response.Error.Code != ErrorCodeInternalError.String() {
2651+
t.Fatalf("response error = %#v, want %q", response.Error, ErrorCodeInternalError.String())
2652+
}
2653+
})
2654+
2655+
t.Run("approve plan unsupported runtime port", func(t *testing.T) {
2656+
response := handleApprovePlanFrame(context.Background(), MessageFrame{
2657+
Type: FrameTypeRequest,
2658+
Action: FrameActionApprovePlan,
2659+
Payload: map[string]any{
2660+
"session_id": "session-1",
2661+
"plan_id": "plan-1",
2662+
"revision": 1,
2663+
},
2664+
}, runtimePortWithoutPlanApproval{RuntimePort: &bootstrapRuntimeStub{}})
2665+
if response.Type != FrameTypeError {
2666+
t.Fatalf("response type = %q, want %q", response.Type, FrameTypeError)
2667+
}
2668+
if response.Error == nil || response.Error.Code != ErrorCodeInternalError.String() {
2669+
t.Fatalf("response error = %#v, want %q", response.Error, ErrorCodeInternalError.String())
2670+
}
2671+
})
2672+
2673+
t.Run("approve plan fills session from frame", func(t *testing.T) {
2674+
stub := &bootstrapRuntimeStub{
2675+
approvePlanFn: func(_ context.Context, input ApprovePlanInput) (ApprovePlanResult, error) {
2676+
if input.SessionID != "session-from-frame" {
2677+
t.Fatalf("session_id = %q, want frame session", input.SessionID)
2678+
}
2679+
return ApprovePlanResult{PlanID: input.PlanID, Revision: input.Revision, Status: "approved"}, nil
2680+
},
2681+
}
2682+
response := handleApprovePlanFrame(context.Background(), MessageFrame{
2683+
Type: FrameTypeRequest,
2684+
Action: FrameActionApprovePlan,
2685+
SessionID: " session-from-frame ",
2686+
Payload: map[string]any{
2687+
"plan_id": "plan-1",
2688+
"revision": 1,
2689+
},
2690+
}, stub)
2691+
if response.Type != FrameTypeAck {
2692+
t.Fatalf("response = %#v, want ack", response)
2693+
}
2694+
if response.SessionID != "session-from-frame" {
2695+
t.Fatalf("response session_id = %q, want frame session", response.SessionID)
2696+
}
2697+
})
2698+
2699+
t.Run("approve plan invalid revision", func(t *testing.T) {
2700+
response := handleApprovePlanFrame(context.Background(), MessageFrame{
2701+
Type: FrameTypeRequest,
2702+
Action: FrameActionApprovePlan,
2703+
Payload: map[string]any{
2704+
"session_id": "session-1",
2705+
"plan_id": "plan-1",
2706+
"revision": 0,
2707+
},
2708+
}, &bootstrapRuntimeStub{})
2709+
if response.Type != FrameTypeError {
2710+
t.Fatalf("response type = %q, want %q", response.Type, FrameTypeError)
2711+
}
2712+
if response.Error == nil || response.Error.Code != ErrorCodeInvalidAction.String() {
2713+
t.Fatalf("response error = %#v, want %q", response.Error, ErrorCodeInvalidAction.String())
2714+
}
2715+
})
2716+
26332717
t.Run("approve plan success", func(t *testing.T) {
26342718
stub := &bootstrapRuntimeStub{
26352719
approvePlanFn: func(ctx context.Context, input ApprovePlanInput) (ApprovePlanResult, error) {

internal/gateway/multi_workspace_runtime_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"os"
77
"path/filepath"
8+
"strings"
89
"sync"
910
"sync/atomic"
1011
"testing"
@@ -548,6 +549,43 @@ func TestMultiWorkspaceRuntime_ApprovePlanRoutesByWorkspace(t *testing.T) {
548549
}
549550
}
550551

552+
func TestMultiWorkspaceRuntime_ApprovePlanErrors(t *testing.T) {
553+
t.Run("workspace not found", func(t *testing.T) {
554+
idx, alpha, _ := setupIndex(t)
555+
builder := newTestBuilder()
556+
mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build)
557+
t.Cleanup(func() { _ = mw.Close() })
558+
559+
_, err := mw.ApprovePlan(ctxWithHash(t, "missing-workspace"), ApprovePlanInput{
560+
SessionID: "session-1",
561+
PlanID: "plan-1",
562+
Revision: 1,
563+
})
564+
if !errors.Is(err, ErrRuntimeResourceNotFound) {
565+
t.Fatalf("ApprovePlan error = %v, want ErrRuntimeResourceNotFound", err)
566+
}
567+
})
568+
569+
t.Run("runtime port does not support plan approval", func(t *testing.T) {
570+
idx, alpha, beta := setupIndex(t)
571+
builder := newTestBuilder()
572+
mw := NewMultiWorkspaceRuntime(idx, alpha.Hash, builder.build)
573+
mw.PreloadWorkspaceBundle(beta.Hash, runtimePortWithoutPlanApproval{
574+
RuntimePort: newRecordingPort("beta"),
575+
}, func() error { return nil })
576+
t.Cleanup(func() { _ = mw.Close() })
577+
578+
_, err := mw.ApprovePlan(ctxWithHash(t, beta.Hash), ApprovePlanInput{
579+
SessionID: "session-1",
580+
PlanID: "plan-1",
581+
Revision: 1,
582+
})
583+
if err == nil || !strings.Contains(err.Error(), "plan approval runtime port is unavailable") {
584+
t.Fatalf("ApprovePlan error = %v, want unsupported plan approval", err)
585+
}
586+
})
587+
}
588+
551589
func TestMultiWorkspaceRuntime_CreatePersistsIndex(t *testing.T) {
552590
idx, alpha, _ := setupIndex(t)
553591
builder := newTestBuilder()

internal/gateway/validate_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,76 @@ func TestValidateFrame_BasicRules(t *testing.T) {
184184
},
185185
wantCode: ErrorCodeInvalidAction.String(),
186186
},
187+
{
188+
name: "approve_plan valid payload",
189+
frame: MessageFrame{
190+
Type: FrameTypeRequest,
191+
Action: FrameActionApprovePlan,
192+
Payload: map[string]any{
193+
"session_id": "session-1",
194+
"plan_id": "plan-1",
195+
"revision": 1,
196+
},
197+
},
198+
wantNil: true,
199+
},
200+
{
201+
name: "approve_plan missing payload",
202+
frame: MessageFrame{
203+
Type: FrameTypeRequest,
204+
Action: FrameActionApprovePlan,
205+
},
206+
wantCode: ErrorCodeMissingRequiredField.String(),
207+
wantField: "payload",
208+
},
209+
{
210+
name: "approve_plan missing session_id",
211+
frame: MessageFrame{
212+
Type: FrameTypeRequest,
213+
Action: FrameActionApprovePlan,
214+
Payload: map[string]any{
215+
"plan_id": "plan-1",
216+
"revision": 1,
217+
},
218+
},
219+
wantCode: ErrorCodeMissingRequiredField.String(),
220+
wantField: "payload.session_id",
221+
},
222+
{
223+
name: "approve_plan missing plan_id",
224+
frame: MessageFrame{
225+
Type: FrameTypeRequest,
226+
Action: FrameActionApprovePlan,
227+
Payload: map[string]any{
228+
"session_id": "session-1",
229+
"revision": 1,
230+
},
231+
},
232+
wantCode: ErrorCodeMissingRequiredField.String(),
233+
wantField: "payload.plan_id",
234+
},
235+
{
236+
name: "approve_plan invalid revision",
237+
frame: MessageFrame{
238+
Type: FrameTypeRequest,
239+
Action: FrameActionApprovePlan,
240+
Payload: map[string]any{
241+
"session_id": "session-1",
242+
"plan_id": "plan-1",
243+
"revision": 0,
244+
},
245+
},
246+
wantCode: ErrorCodeInvalidAction.String(),
247+
},
248+
{
249+
name: "approve_plan invalid payload shape",
250+
frame: MessageFrame{
251+
Type: FrameTypeRequest,
252+
Action: FrameActionApprovePlan,
253+
Payload: "bad-payload",
254+
},
255+
wantCode: ErrorCodeInvalidFrame.String(),
256+
},
187257
{
188258
name: "event frame allows empty action",
189259
frame: MessageFrame{

0 commit comments

Comments
 (0)