package permission import ( "context" "sync/atomic" "testing" ) // TestCompositeHandler_SingleHandlerDegradation 单处理器退化 func TestCompositeHandler_SingleHandlerDegradation(t *testing.T) { handler := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow, Reason: "approved"}, nil } ch := NewCompositeHandler(NamedHandler{ Name: "only", Handler: handler, IsDecisionMaker: true, }) resp, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Decision != DecisionAllow { t.Errorf("Decision = %q, want %q", resp.Decision, DecisionAllow) } if resp.Reason != "approved" { t.Errorf("Reason = %q, want %q", resp.Reason, "approved") } } // TestCompositeHandler_DecisionMakerAndObserver 决策者 + 观察者分离 func TestCompositeHandler_DecisionMakerAndObserver(t *testing.T) { var auditCalled atomic.Bool decisionHandler := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow, Reason: "user approved"}, nil } auditHandler := func(ctx context.Context, req *Request) (*Response, error) { auditCalled.Store(true) // 观察者也返回响应,但不应影响决策 return &Response{Decision: DecisionDeny, Reason: "audit says deny"}, nil } ch := NewCompositeHandler( NamedHandler{Name: "cli", Handler: decisionHandler, IsDecisionMaker: true}, NamedHandler{Name: "audit", Handler: auditHandler, IsDecisionMaker: false}, ) resp, err := ch.Handle(context.Background(), &Request{ToolName: "Edit"}) if err != nil { t.Fatalf("unexpected error: %v", err) } // 决策应来自决策者 if resp.Decision != DecisionAllow { t.Errorf("Decision = %q, want %q (from decision maker)", resp.Decision, DecisionAllow) } // 审计应被调用 if !auditCalled.Load() { t.Error("审计处理器应被调用") } } // TestCompositeHandler_MultipleDecisionMakers 多决策者取第一个 func TestCompositeHandler_MultipleDecisionMakers(t *testing.T) { first := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow, Reason: "first"}, nil } second := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionDeny, Reason: "second"}, nil } ch := NewCompositeHandler( NamedHandler{Name: "first", Handler: first, IsDecisionMaker: true}, NamedHandler{Name: "second", Handler: second, IsDecisionMaker: true}, ) resp, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Decision != DecisionAllow { t.Errorf("Decision = %q, want %q (first decision maker wins)", resp.Decision, DecisionAllow) } if resp.Reason != "first" { t.Errorf("Reason = %q, want %q", resp.Reason, "first") } } // TestCompositeHandler_AllHandlersExecuted 所有处理器都执行 func TestCompositeHandler_AllHandlersExecuted(t *testing.T) { var count atomic.Int32 makeHandler := func() Handler { return func(ctx context.Context, req *Request) (*Response, error) { count.Add(1) return &Response{Decision: DecisionAllow, Reason: "ok"}, nil } } ch := NewCompositeHandler( NamedHandler{Name: "a", Handler: makeHandler(), IsDecisionMaker: true}, NamedHandler{Name: "b", Handler: makeHandler(), IsDecisionMaker: false}, NamedHandler{Name: "c", Handler: makeHandler(), IsDecisionMaker: false}, ) _, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if count.Load() != 3 { t.Errorf("处理器执行次数 = %d, 期望 3(所有处理器都应执行)", count.Load()) } } // TestCompositeHandler_NoDecisionMaker 无决策者时默认 Deny func TestCompositeHandler_NoDecisionMaker(t *testing.T) { observer := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow, Reason: "observer"}, nil } ch := NewCompositeHandler( NamedHandler{Name: "audit", Handler: observer, IsDecisionMaker: false}, ) resp, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Decision != DecisionDeny { t.Errorf("Decision = %q, want %q (no decision maker → deny)", resp.Decision, DecisionDeny) } } // TestCompositeHandler_EmptyHandlers 无处理器时默认 Deny func TestCompositeHandler_EmptyHandlers(t *testing.T) { ch := NewCompositeHandler() resp, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Decision != DecisionDeny { t.Errorf("Decision = %q, want %q (empty handlers → deny)", resp.Decision, DecisionDeny) } } // TestCompositeHandler_DecisionMakerReturnsNil 决策者返回 nil 时跳过 func TestCompositeHandler_DecisionMakerReturnsNil(t *testing.T) { nilHandler := func(ctx context.Context, req *Request) (*Response, error) { return nil, nil } realHandler := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow, Reason: "real"}, nil } ch := NewCompositeHandler( NamedHandler{Name: "nil", Handler: nilHandler, IsDecisionMaker: true}, NamedHandler{Name: "real", Handler: realHandler, IsDecisionMaker: true}, ) resp, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Decision != DecisionAllow { t.Errorf("Decision = %q, want %q (skip nil, use second)", resp.Decision, DecisionAllow) } if resp.Reason != "real" { t.Errorf("Reason = %q, want %q", resp.Reason, "real") } } // TestCompositeHandler_DynamicAddRemove 动态添加/移除 func TestCompositeHandler_DynamicAddRemove(t *testing.T) { ch := NewCompositeHandler() if ch.Len() != 0 { t.Fatalf("初始处理器数 = %d, 期望 0", ch.Len()) } handler := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow}, nil } ch.Add(NamedHandler{Name: "a", Handler: handler, IsDecisionMaker: true}) if ch.Len() != 1 { t.Errorf("添加后处理器数 = %d, 期望 1", ch.Len()) } removed := ch.Remove("a") if !removed { t.Error("移除 'a' 应返回 true") } if ch.Len() != 0 { t.Errorf("移除后处理器数 = %d, 期望 0", ch.Len()) } removed = ch.Remove("nonexistent") if removed { t.Error("移除不存在的处理器应返回 false") } } // TestCompositeHandler_NilHandlerFiltered 空 Handler 被过滤 func TestCompositeHandler_NilHandlerFiltered(t *testing.T) { ch := NewCompositeHandler( NamedHandler{Name: "nil", Handler: nil, IsDecisionMaker: true}, NamedHandler{Name: "real", Handler: func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow}, nil }, IsDecisionMaker: true}, ) if ch.Len() != 1 { t.Errorf("nil Handler 应被过滤, 处理器数 = %d, 期望 1", ch.Len()) } } // TestCompositeHandler_AddNilIgnored 添加 nil Handler 被忽略 func TestCompositeHandler_AddNilIgnored(t *testing.T) { ch := NewCompositeHandler() ch.Add(NamedHandler{Name: "nil", Handler: nil}) if ch.Len() != 0 { t.Errorf("添加 nil Handler 后处理器数 = %d, 期望 0", ch.Len()) } } // TestCompositeHandler_BackwardCompatibility 向后兼容(单 Handler → CompositeHandler) func TestCompositeHandler_BackwardCompatibility(t *testing.T) { // 模拟原始用法:单个 Handler originalHandler := func(ctx context.Context, req *Request) (*Response, error) { return &Response{Decision: DecisionAllow, Reason: "original"}, nil } // 包装为 CompositeHandler 的第一个 DecisionMaker ch := NewCompositeHandler(NamedHandler{ Name: "original", Handler: originalHandler, IsDecisionMaker: true, }) resp, err := ch.Handle(context.Background(), &Request{ToolName: "Bash"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.Decision != DecisionAllow || resp.Reason != "original" { t.Errorf("向后兼容失败: Decision=%q Reason=%q", resp.Decision, resp.Reason) } }