Files
Agent-System-Backend/internal/server/action_matrix_test.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

90 lines
3.1 KiB
Go

package server
import (
"testing"
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
// 协作存储 U3 切片 2(KTD13):动作授权矩阵。未配置的动作默认拒绝;
// 旧只读 EXT-8 适配器只保留 read 能力;action-aware 适配器注册后权威。
func newMatrixServer(t *testing.T) *Server {
t.Helper()
s := &Server{}
t.Cleanup(func() {})
return s
}
// 无任何适配器:全部动作拒绝(默认拒绝;与 EXT-8 未注册时读取 NotFound 同基调)。
func TestActionMatrixDefaultDeny(t *testing.T) {
s := newMatrixServer(t)
for _, a := range []ProjectAction{ProjectRead, ProjectEdit, ProjectApprove, ProjectExecute, ProjectAdmin} {
if s.authorizeProjectAction("alice", "p1", a) {
t.Fatalf("未注册适配器时 %s 应默认拒绝", a)
}
}
}
// 旧只读适配器:read 放行、其余动作不得自动获得写入能力(KTD13 兼容语义)。
func TestActionMatrixLegacyAdapterReadOnly(t *testing.T) {
s := newMatrixServer(t)
s.SetProjectAuthorizer(func(owner string, projectID core.ID) (bool, error) {
return owner == "alice", nil
})
if !s.authorizeProjectAction("alice", "p1", ProjectRead) {
t.Fatal("alice read 应放行(旧适配器)")
}
for _, a := range []ProjectAction{ProjectEdit, ProjectApprove, ProjectExecute, ProjectAdmin} {
if s.authorizeProjectAction("alice", "p1", a) {
t.Fatalf("旧只读适配器不得自动获得 %s 能力", a)
}
}
// 他人(动作越权):read 同样拒绝。
if s.authorizeProjectAction("bob", "p1", ProjectRead) {
t.Fatal("bob 越权 read 应拒绝")
}
}
// action-aware 适配器:注册后权威(read 与显式配置的动作放行,未配置动作
// 默认拒绝;出错 fail-closed)。
func TestActionMatrixActionAwareAuthoritative(t *testing.T) {
s := newMatrixServer(t)
// 同时注册旧适配器:action-aware 存在时不得回落旧适配器(权威唯一)。
s.SetProjectAuthorizer(func(owner string, projectID core.ID) (bool, error) {
return true, nil
})
s.SetProjectActionAuthorizer(func(owner string, projectID core.ID, action ProjectAction) (bool, error) {
switch action {
case ProjectRead, ProjectEdit:
return owner == "alice", nil
default:
return false, nil // approve/execute/admin 未授予
}
})
if !s.authorizeProjectAction("alice", "p1", ProjectRead) || !s.authorizeProjectAction("alice", "p1", ProjectEdit) {
t.Fatal("alice read/edit 应放行")
}
for _, a := range []ProjectAction{ProjectApprove, ProjectExecute, ProjectAdmin} {
if s.authorizeProjectAction("alice", "p1", a) {
t.Fatalf("未授予的 %s 应默认拒绝", a)
}
}
if s.authorizeProjectAction("bob", "p1", ProjectEdit) {
t.Fatal("bob 越权 edit 应拒绝")
}
// 适配器出错:fail-closed(错误不区分,避免泄露存在性)。
s.SetProjectActionAuthorizer(func(owner string, projectID core.ID, action ProjectAction) (bool, error) {
return false, errBoom
})
if s.authorizeProjectAction("alice", "p1", ProjectRead) {
t.Fatal("适配器出错应 fail-closed")
}
}
var errBoom = &boomErr{}
type boomErr struct{}
func (*boomErr) Error() string { return "boom" }