Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
117 lines
3.9 KiB
Go
117 lines
3.9 KiB
Go
package decision_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/netkingdom/access-engine/internal/decision"
|
|
"github.com/netkingdom/access-engine/pkg/api"
|
|
)
|
|
|
|
// failingRecorder is an outbox whose commit always fails.
|
|
type failingRecorder struct {
|
|
appends int
|
|
batches int
|
|
uncommitted []api.DecisionEffect
|
|
}
|
|
|
|
func (r *failingRecorder) Append(api.DecisionEnvelope) error {
|
|
r.appends++
|
|
return errors.New("disk full")
|
|
}
|
|
|
|
func (r *failingRecorder) AppendBatch([]api.DecisionEnvelope) error {
|
|
r.batches++
|
|
return errors.New("disk full")
|
|
}
|
|
|
|
func (r *failingRecorder) NoteReleasedUncommitted(effect api.DecisionEffect) {
|
|
r.uncommitted = append(r.uncommitted, effect)
|
|
}
|
|
|
|
var (
|
|
allowedRead = api.CheckRequest{
|
|
Subject: api.SubjectRef{ID: "user:alice"},
|
|
Action: "read",
|
|
Resource: api.ResourceRef{ID: "document:internal-note", System: "markitect-tool"},
|
|
}
|
|
deniedRead = api.CheckRequest{
|
|
Subject: api.SubjectRef{ID: "user:alice"},
|
|
Action: "read",
|
|
Resource: api.ResourceRef{ID: "document:missing", Type: "document", System: "markitect-tool"},
|
|
}
|
|
)
|
|
|
|
// GH-DEC-2026-024: a restriction is not exempt from required commitment.
|
|
func TestUncommittedDenyIsWithheld(t *testing.T) {
|
|
engine := newTestEngine(t)
|
|
recorder := &failingRecorder{}
|
|
engine.SetDecisionLog(recorder)
|
|
got, err := engine.Check(context.Background(), deniedRead)
|
|
if !errors.Is(err, decision.ErrRecordNotCommitted) || got.ID != "" {
|
|
t.Fatalf("Check = %+v, %v; want unavailable and no decision", got, err)
|
|
}
|
|
if len(recorder.uncommitted) != 0 {
|
|
t.Fatalf("withheld restriction counted as released: %v", recorder.uncommitted)
|
|
}
|
|
engine.SetDecisionLog(nil)
|
|
committed, err := engine.Check(context.Background(), deniedRead)
|
|
if err != nil || committed.Effect != api.DecisionEffectDeny {
|
|
t.Fatalf("normal deny = %+v, %v", committed, err)
|
|
}
|
|
}
|
|
|
|
// FLEX-DEC-2026-018: authority is never granted without its evidence.
|
|
func TestUncommittedAllowIsWithheld(t *testing.T) {
|
|
engine := newTestEngine(t)
|
|
recorder := &failingRecorder{}
|
|
engine.SetDecisionLog(recorder)
|
|
|
|
got, err := engine.Check(context.Background(), allowedRead)
|
|
if !errors.Is(err, decision.ErrRecordNotCommitted) {
|
|
t.Fatalf("err = %v, decision = %+v; want ErrRecordNotCommitted", err, got)
|
|
}
|
|
if len(recorder.uncommitted) != 0 {
|
|
t.Fatalf("a withheld allow was counted as released: %v", recorder.uncommitted)
|
|
}
|
|
}
|
|
|
|
func TestBatchCommitsOnceAndWithholdsOnAnyUncommittedAllow(t *testing.T) {
|
|
engine := newTestEngine(t)
|
|
recorder := &failingRecorder{}
|
|
engine.SetDecisionLog(recorder)
|
|
|
|
_, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{
|
|
Subject: allowedRead.Subject,
|
|
Action: "read",
|
|
Resources: []api.ResourceRef{allowedRead.Resource, deniedRead.Resource},
|
|
})
|
|
if !errors.Is(err, decision.ErrRecordNotCommitted) {
|
|
t.Fatalf("err = %v; want ErrRecordNotCommitted for a batch holding an allow", err)
|
|
}
|
|
if recorder.batches != 1 || recorder.appends != 0 {
|
|
t.Fatalf("batches=%d appends=%d; want one batch commit", recorder.batches, recorder.appends)
|
|
}
|
|
|
|
decisions, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{
|
|
Subject: deniedRead.Subject,
|
|
Action: "read",
|
|
Resources: []api.ResourceRef{deniedRead.Resource},
|
|
})
|
|
if !errors.Is(err, decision.ErrRecordNotCommitted) || len(decisions) != 0 {
|
|
t.Fatalf("restriction-only batch: decisions=%v err=%v; want withheld", decisions, err)
|
|
}
|
|
}
|
|
|
|
func TestListAllowedWithholdsWhenAllCandidateDenialsFailCommit(t *testing.T) {
|
|
engine := newTestEngine(t)
|
|
recorder := &failingRecorder{}
|
|
engine.SetDecisionLog(recorder)
|
|
got, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{
|
|
Subject: deniedRead.Subject, Action: "unknown-action", System: "markitect-tool",
|
|
})
|
|
if !errors.Is(err, decision.ErrRecordNotCommitted) || got != nil || recorder.batches != 1 {
|
|
t.Fatalf("ListAllowed = %v, %v; batches=%d; want unavailable", got, err, recorder.batches)
|
|
}
|
|
}
|