100 lines
4.4 KiB
Go
100 lines
4.4 KiB
Go
package extension
|
|
|
|
// W9 X2 读取面补完(2.29.0-experimental)的公开面契约:
|
|
// 1. 新增项全部是可选字段/新导出类型:既有 ProjectAuditHost、ProjectAuditRegistry
|
|
// 提供者与既有查询字面量照旧编译、照旧语义(不破坏旧 provider);
|
|
// 2. 新字段序列化带 omitempty:未使用的读取请求与旧消费者的线格式不变;
|
|
// 3. 正文投影与导出声明是显式契约字段,未开启时不下发。
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
// legacyAuditHost 是 2.14.0 形态的消费者:只实现既有方法,不感知新增可选字段。
|
|
type legacyAuditHost struct{}
|
|
|
|
func (legacyAuditHost) QueryProjectAudit(context.Context, ProjectAuditQuery) (ProjectAuditPage, error) {
|
|
return ProjectAuditPage{}, nil
|
|
}
|
|
|
|
// legacyAuditRegistry 是 2.14.0 形态的注册方实现。
|
|
type legacyAuditRegistry struct{}
|
|
|
|
func (legacyAuditRegistry) RegisterProjectAuditAuthorizer(ProjectAuditAuthorizer) error { return nil }
|
|
|
|
var (
|
|
_ ProjectAuditHost = legacyAuditHost{}
|
|
_ ProjectAuditRegistry = legacyAuditRegistry{}
|
|
)
|
|
|
|
func TestProjectAuditReadContractIsAdditiveAndOptional(t *testing.T) {
|
|
if SurfaceVersion < "2.29.0" {
|
|
t.Fatalf("SurfaceVersion must record the audit read completion, got %s", SurfaceVersion)
|
|
}
|
|
// 既有查询字面量(只填项目与游标)仍然可用,新增字段全部零值。
|
|
legacy := ProjectAuditQuery{ProjectID: core.ID("p1"), Limit: 10, BeforeOffset: 4, CutoffOffset: 9}
|
|
if legacy.IncludeBody || legacy.Export || legacy.LinkedResourceID != "" || legacy.LinkedRunID != "" {
|
|
t.Fatalf("optional fields defaulted to non-zero: %+v", legacy)
|
|
}
|
|
raw, err := json.Marshal(legacy)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, unwanted := range []string{"includeBody", "export", "linkedResourceKind", "linkedResourceId", "linkedRunId"} {
|
|
if strings.Contains(string(raw), unwanted) {
|
|
t.Fatalf("omitted optional field %s reached the wire: %s", unwanted, raw)
|
|
}
|
|
}
|
|
for _, want := range []string{`"projectId":"p1"`, `"beforeOffset":4`, `"cutoffOffset":9`, `"limit":10`} {
|
|
if !strings.Contains(string(raw), want) {
|
|
t.Fatalf("existing field %s missing from the wire form: %s", want, raw)
|
|
}
|
|
}
|
|
// 显式请求时字段可见,消费端能区分“正文/导出请求”与普通分页。
|
|
full, err := json.Marshal(ProjectAuditQuery{ProjectID: core.ID("p1"), IncludeBody: true, Export: true,
|
|
LinkedResourceKind: "document", LinkedResourceID: "doc-1", LinkedRunID: "run-1"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{`"includeBody":true`, `"export":true`, `"linkedResourceKind":"document"`,
|
|
`"linkedResourceId":"doc-1"`, `"linkedRunId":"run-1"`} {
|
|
if !strings.Contains(string(full), want) {
|
|
t.Fatalf("requested field %s missing: %s", want, full)
|
|
}
|
|
}
|
|
// 未授予正文时回执不带 resource 字段;授予后才出现对象身份(不含正文载荷)。
|
|
metadataOnly, err := json.Marshal(ProjectAuditPage{Receipts: []HostAuditReceipt{{SourceOffset: 3, CommandID: "rdm.confirm"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(metadataOnly), `"resource"`) {
|
|
t.Fatalf("metadata-only receipt carried a resource: %s", metadataOnly)
|
|
}
|
|
withBody, err := json.Marshal(ProjectAuditPage{Receipts: []HostAuditReceipt{{SourceOffset: 3, CommandID: "rdm.confirm",
|
|
Resource: &ProjectAuditReceiptResource{Kind: "document", ID: "doc-1", RunID: "run-1"}}},
|
|
Coverage: ProjectAuditCoverage{ResourceBodies: "verified-exact-link-body", Export: "unsupported"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{`"resource":{"kind":"document","id":"doc-1","runId":"run-1"}`, `"export":"unsupported"`} {
|
|
if !strings.Contains(string(withBody), want) {
|
|
t.Fatalf("body-permitted projection missing %s: %s", want, withBody)
|
|
}
|
|
}
|
|
// 四个审计哨兵互不相等:unsupported(缺基座能力)与 denied(策略拒绝)
|
|
// 不得互相冒充。
|
|
if !errors.Is(ErrProjectAuditUnsupported, ErrProjectAuditUnsupported) {
|
|
t.Fatal("unsupported sentinel must stay comparable with errors.Is")
|
|
}
|
|
for _, distinct := range []error{ErrProjectAuditDenied, ErrProjectAuditLinkConflict, ErrProjectAuditPending} {
|
|
if errors.Is(ErrProjectAuditUnsupported, distinct) || errors.Is(distinct, ErrProjectAuditUnsupported) {
|
|
t.Fatalf("audit sentinels must stay distinct: %v vs %v", distinct, ErrProjectAuditUnsupported)
|
|
}
|
|
}
|
|
}
|