Non-author triggers were denied by dutyAuthorizer because AutoCommitUserID was the triggering user. Record that user on OnBehalfOf and surface the duty mismatch in the task error.
186 lines
5.9 KiB
Go
186 lines
5.9 KiB
Go
package docflow
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/app/uow"
|
|
"chat-agent-system/internal/docgov"
|
|
"chat-agent-system/internal/docrevision"
|
|
)
|
|
|
|
func TestHumanEditCharacterizationSameETagOneWinner(t *testing.T) {
|
|
ctx := context.Background()
|
|
rev := docrevision.NewService(nil)
|
|
st, err := rev.OpenTemplateVersion(ctx, "doc_flow_etag", "# base\n", docrevision.ActorSnapshot{}, 1, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmds := &Commands{Rev: rev}
|
|
var wg sync.WaitGroup
|
|
var mu sync.Mutex
|
|
wins, losses := 0, 0
|
|
for i := 0; i < 8; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
_, err := cmds.HumanEdit(ctx, Principal{UserID: int64(i + 1)}, HumanEditCommand{
|
|
DocID: "doc_flow_etag",
|
|
Request: docrevision.HumanRevisionRequest{
|
|
DocID: "doc_flow_etag", Markdown: fmt.Sprintf("# base\n\nedit %d\n", i),
|
|
ExpectedHeadSnapshot: &st.HeadSnapshotID, ExpectedRevisionNo: &st.HeadRevisionNo,
|
|
IdempotencyKey: fmt.Sprintf("flow-%d", i), ActorUserID: int64(i + 1), LifecycleState: "drafting",
|
|
},
|
|
})
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if err == nil {
|
|
wins++
|
|
} else if err == docrevision.ErrHeadConflict {
|
|
losses++
|
|
} else {
|
|
t.Errorf("unexpected %v", err)
|
|
}
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
if wins != 1 || losses != 7 {
|
|
t.Fatalf("wins=%d losses=%d", wins, losses)
|
|
}
|
|
got, err := rev.GetRevisionState(ctx, "doc_flow_etag")
|
|
if err != nil || got.HeadRevisionNo != 1 {
|
|
t.Fatalf("head=%+v err=%v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestApproveRejectCharacterizationOneTerminal(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov := docgov.NewGovernanceEngine(nil)
|
|
doc, err := gov.CreateOrGetDocument(ctx, "doc_flow_appr", 1, "intake", "t", "V0.5_feasibility", "", 1, "A", 2, "R", 3, "P")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := gov.SubmitForReview(ctx, doc.DocID, 1, docgov.RoleAuthor, "A"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := gov.ReviewApprove(ctx, doc.DocID, 2, docgov.RoleReviewer, "R", "ok"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmds := &Commands{Gov: gov}
|
|
var wg sync.WaitGroup
|
|
results := make(chan string, 2)
|
|
wg.Add(2)
|
|
go func() {
|
|
defer wg.Done()
|
|
doc, err := cmds.Approve(ctx, Principal{UserID: 3, Role: docgov.RoleApprover, Name: "P"}, DecisionCommand{DocID: doc.DocID})
|
|
if err == nil && doc != nil {
|
|
results <- string(doc.CurrentState)
|
|
} else {
|
|
results <- "err"
|
|
}
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
doc, err := cmds.Reject(ctx, Principal{UserID: 3, Role: docgov.RoleApprover, Name: "P"}, DecisionCommand{
|
|
DocID: doc.DocID, RejectReason: "no", RollbackTo: docgov.StateDrafting,
|
|
})
|
|
if err == nil && doc != nil {
|
|
results <- string(doc.CurrentState)
|
|
} else {
|
|
results <- "err"
|
|
}
|
|
}()
|
|
wg.Wait()
|
|
close(results)
|
|
var states []string
|
|
for s := range results {
|
|
states = append(states, s)
|
|
}
|
|
if len(states) != 2 {
|
|
t.Fatalf("%v", states)
|
|
}
|
|
approved, rejected := 0, 0
|
|
for _, s := range states {
|
|
switch s {
|
|
case string(docgov.StateApproved):
|
|
approved++
|
|
case string(docgov.StateDrafting), string(docgov.StateRejected):
|
|
rejected++
|
|
}
|
|
}
|
|
if approved+rejected != 1 {
|
|
t.Fatalf("expected one legal terminal, got %v", states)
|
|
}
|
|
}
|
|
|
|
func TestIdempotentHumanEditSameDigest(t *testing.T) {
|
|
ctx := context.Background()
|
|
rev := docrevision.NewService(nil)
|
|
st, err := rev.OpenTemplateVersion(ctx, "doc_flow_idemp", "# base\n", docrevision.ActorSnapshot{}, 1, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmds := &Commands{Rev: rev}
|
|
req := docrevision.HumanRevisionRequest{
|
|
DocID: "doc_flow_idemp", Markdown: "# base\n\nedit\n",
|
|
ExpectedHeadSnapshot: &st.HeadSnapshotID, ExpectedRevisionNo: &st.HeadRevisionNo,
|
|
IdempotencyKey: "same-key", ActorUserID: 1, LifecycleState: "drafting",
|
|
}
|
|
first, err := cmds.HumanEdit(ctx, Principal{UserID: 1}, HumanEditCommand{DocID: req.DocID, Request: req})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := cmds.HumanEdit(ctx, Principal{UserID: 1}, HumanEditCommand{DocID: req.DocID, Request: req})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.State.HeadSnapshotID != second.State.HeadSnapshotID {
|
|
t.Fatal("same key+digest must replay")
|
|
}
|
|
req.Markdown = "# base\n\nother digest\n"
|
|
if _, err := cmds.HumanEdit(ctx, Principal{UserID: 1}, HumanEditCommand{DocID: req.DocID, Request: req}); err != docrevision.ErrIdempotencyConflict {
|
|
t.Fatalf("different digest: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDutyAuthorizerAllowsDocumentAuthorCommit(t *testing.T) {
|
|
rec := &docgov.DocumentRecord{DocID: "doc_flow_author_commit", AuthorID: 4, ApproverID: 9}
|
|
auth := DutyAuthorizer()
|
|
if err := auth.Authorize(context.Background(), Principal{UserID: 4}, rec, ActionCommit); err != nil {
|
|
t.Fatalf("document author must pass agent commit: %v", err)
|
|
}
|
|
if err := auth.Authorize(context.Background(), Principal{UserID: 74}, rec, ActionCommit); err != uow.ErrPermissionDenied {
|
|
t.Fatalf("non-author commit must be denied, got %v", err)
|
|
}
|
|
if err := auth.Authorize(context.Background(), Principal{UserID: 4}, rec, ActionEdit); err != nil {
|
|
t.Fatalf("document author must still pass edit: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReauthDeniesWrongAuthor(t *testing.T) {
|
|
ctx := context.Background()
|
|
gov := docgov.NewGovernanceEngine(nil)
|
|
if _, err := gov.CreateOrGetDocument(ctx, "doc_flow_authz", 1, "intake", "t", "V0.5_feasibility", "", 1, "A", 2, "R", 3, "P"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rev := docrevision.NewService(nil)
|
|
st, err := rev.OpenTemplateVersion(ctx, "doc_flow_authz", "# base\n", docrevision.ActorSnapshot{}, 1, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmds := &Commands{Gov: gov, Rev: rev}
|
|
_, err = cmds.HumanEdit(ctx, Principal{UserID: 99}, HumanEditCommand{
|
|
DocID: "doc_flow_authz",
|
|
Request: docrevision.HumanRevisionRequest{
|
|
DocID: "doc_flow_authz", Markdown: "# base\n\nnope\n",
|
|
ExpectedHeadSnapshot: &st.HeadSnapshotID, ExpectedRevisionNo: &st.HeadRevisionNo,
|
|
IdempotencyKey: "authz-deny", ActorUserID: 99, LifecycleState: "drafting",
|
|
},
|
|
})
|
|
if err != uow.ErrPermissionDenied {
|
|
t.Fatalf("want permission denied, got %v", err)
|
|
}
|
|
}
|