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
74 lines
3.3 KiB
Go
74 lines
3.3 KiB
Go
package app
|
||
|
||
// EXT-8b 资源级授权槽位测试(U9 首切片):
|
||
// 1. V2 未注册 → AuthorizeResource 回落 V1(owner×project 兼容现行);
|
||
// 2. V2 注册 → 按查询六要素咨询;受信字段缺失(actor/action/type/id 空)=拒绝;
|
||
// 3. V2 返回 error = 拒绝(fail-closed);
|
||
// 4. 首注册者生效(后来者忽略);
|
||
// 5. compiledRegistry 的 V2 注册被拒(声明式无宿主回调)。
|
||
import (
|
||
"errors"
|
||
"testing"
|
||
|
||
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/pkg/extension"
|
||
)
|
||
|
||
func TestAuthorizeResourceV2FallbackAndQuery(t *testing.T) {
|
||
var a projectAuthz
|
||
// 未注册任何适配器 → 拒绝。
|
||
if ok, err := a.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1",
|
||
Owner: "u2", ProjectID: "p1"}); ok || err != nil {
|
||
t.Fatalf("未注册应拒绝, got %v %v", ok, err)
|
||
}
|
||
// 仅 V1:回落 owner×project 布尔。
|
||
a.set("p1", func(owner string, projectID string) (bool, error) { return true, nil })
|
||
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1",
|
||
Owner: "u2", ProjectID: "p1"}); !ok {
|
||
t.Fatal("V1 允许时回落应放行")
|
||
}
|
||
// V1 路径要求 owner/project 非空。
|
||
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1"}); ok {
|
||
t.Fatal("V1 回落在无项目私域应拒绝")
|
||
}
|
||
// 注册 V2 → 按六要素咨询。
|
||
a.setV2("p1", func(q extension.ResourceAuthzQuery) (bool, error) {
|
||
return q.Actor == "u1" && q.Action == "download" && q.ResourceType == "artifact" &&
|
||
q.ResourceID == "a1" && q.Owner == "u2" && q.ProjectID == "p1", nil
|
||
})
|
||
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u1", Action: "download", ResourceType: "artifact", ResourceID: "a1",
|
||
Owner: "u2", ProjectID: "p1"}); !ok {
|
||
t.Fatal("V2 六要素齐备应放行")
|
||
}
|
||
// 六要素缺一(action 空)=拒绝(不可判定)。
|
||
if ok, _ := a.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u1", ResourceType: "artifact", ResourceID: "a1", Owner: "u2", ProjectID: "p1"}); ok {
|
||
t.Fatal("受信字段缺失应拒绝")
|
||
}
|
||
// V2 出错=拒绝(fail-closed)。
|
||
a2 := projectAuthz{}
|
||
a2.setV2("p1", func(extension.ResourceAuthzQuery) (bool, error) { return false, errors.New("boom") })
|
||
if ok, err := a2.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u", Action: "read", ResourceType: "x", ResourceID: "1"}); ok || err == nil {
|
||
t.Fatal("V2 error 应拒绝")
|
||
}
|
||
// 首注册者生效。
|
||
a3 := projectAuthz{}
|
||
a3.setV2("first", func(extension.ResourceAuthzQuery) (bool, error) { return true, nil })
|
||
a3.setV2("second", func(extension.ResourceAuthzQuery) (bool, error) { return false, nil })
|
||
if ok, _ := a3.AuthorizeResource(extension.ResourceAuthzQuery{
|
||
Actor: "u", Action: "read", ResourceType: "x", ResourceID: "1"}); !ok {
|
||
t.Fatal("首个 V2 注册者应生效")
|
||
}
|
||
}
|
||
|
||
func TestCompiledRegistryRejectsV2(t *testing.T) {
|
||
r := compiledRegistry{}
|
||
if err := r.RegisterResourceAuthorizerV2(func(extension.ResourceAuthzQuery) (bool, error) { return true, nil }); err == nil {
|
||
t.Fatal("声明式扩展不得注册资源级授权回调(fail-closed)")
|
||
}
|
||
}
|