Why: An interrupted import cleanup removed the cmdbus core import while two human-decision files used an explicit alias. Evidence: go test ./internal/cmdbus ./internal/server passes. Impact: The backend worktree is buildable and consistently names the core package without behavior changes. Refs: W4b human decision verifier
106 lines
3.8 KiB
Go
106 lines
3.8 KiB
Go
package server
|
|
|
|
import (
|
|
core "agent-system-core"
|
|
"context"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
agentcommand "agent-command"
|
|
"agent-system-backend/internal/cmdbus"
|
|
"agent-system-backend/internal/store"
|
|
"agent-system-backend/pkg/extension"
|
|
)
|
|
|
|
type nopHumanAudit struct{}
|
|
|
|
func (nopHumanAudit) AppendAudit(string, agentcommand.AuditEnvelope) error { return nil }
|
|
|
|
func TestResolveHumanDecisionBindsFollowUpAndRetry(t *testing.T) {
|
|
st, err := store.OpenSQLite(t.TempDir() + "/decision.db")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
issued, err := st.IssueHumanDecision(store.HumanDecision{
|
|
PluginID: "rdm", Audience: extension.HumanDecisionAudienceStructure,
|
|
CommandID: "rdm.structure.decide", HumanActor: "alice", Owner: "alice",
|
|
ProjectID: "p1", ProposalID: "prop-1", ProposalRevision: 1,
|
|
PayloadHash: "hash-1", Question: "确认结构变更",
|
|
}, time.Now().UTC())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var got *cmdbus.HumanGrant
|
|
bus := cmdbus.New(nopHumanAudit{}, nil, nil)
|
|
if err := bus.RegisterPluginCommand(agentcommand.CommandSpec{ID: "rdm.structure.decide", Title: "确认结构"},
|
|
func(_ context.Context, req cmdbus.Request) (any, error) {
|
|
got = req.HumanGrant
|
|
if req.Params["confirmed"] != nil {
|
|
t.Fatal("follow-up copied a client confirmation flag")
|
|
}
|
|
return map[string]any{"applied": true}, nil
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := &Server{st: st, Bus: bus}
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/api/hitl/"+issued.ID+"/resolve", nil)
|
|
req = req.WithContext(context.WithValue(req.Context(), ownerCtxKey{}, "alice"))
|
|
handled, err := s.resolveHumanDecision(rec, req, core.HITLResponse{CardID: core.ID(issued.ID), ChoiceID: "accept"})
|
|
if err != nil || !handled {
|
|
t.Fatalf("resolve handled=%v err=%v body=%s", handled, err, rec.Body.String())
|
|
}
|
|
if got == nil || got.Actor != "alice" || got.ProofID == "" || got.CardID != issued.ID {
|
|
t.Fatalf("follow-up grant = %+v", got)
|
|
}
|
|
grant, ok := s.sessionHumanGrant("alice", "rdm.structure.decide", map[string]any{
|
|
"projectId": "p1", "proposalId": "prop-1", "proposalRevision": float64(1),
|
|
"payloadHash": "hash-1", "decision": "accept", "confirmed": true,
|
|
})
|
|
if !ok || grant.ProofID != got.ProofID {
|
|
t.Fatalf("retry grant = %+v ok=%v", grant, ok)
|
|
}
|
|
if _, ok := s.sessionHumanGrant("bob", "rdm.structure.decide", map[string]any{
|
|
"projectId": "p1", "proposalId": "prop-1", "proposalRevision": float64(1),
|
|
"payloadHash": "hash-1", "decision": "accept",
|
|
}); ok {
|
|
t.Fatal("another actor received the grant")
|
|
}
|
|
}
|
|
|
|
func TestNaturalLanguageApprovalRequiresOnePendingCard(t *testing.T) {
|
|
st, err := store.OpenSQLite(t.TempDir() + "/decision.db")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
now := time.Now().UTC()
|
|
first, err := st.IssueHumanDecision(store.HumanDecision{
|
|
PluginID: "rdm", Audience: extension.HumanDecisionAudienceStructure,
|
|
CommandID: "rdm.structure.decide", HumanActor: "alice", Owner: "alice",
|
|
ProjectID: "p1", ProposalID: "prop-1", ProposalRevision: 1,
|
|
PayloadHash: "hash-1", Question: "第一张",
|
|
}, now)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := st.IssueHumanDecision(store.HumanDecision{
|
|
PluginID: "rdm", Audience: extension.HumanDecisionAudienceStructure,
|
|
CommandID: "rdm.structure.decide", HumanActor: "alice", Owner: "alice",
|
|
ProjectID: "p1", ProposalID: "prop-2", ProposalRevision: 1,
|
|
PayloadHash: "hash-2", Question: "第二张",
|
|
}, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
s := &Server{st: st}
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/api/hitl/"+first.ID+"/resolve", nil)
|
|
req = req.WithContext(context.WithValue(req.Context(), ownerCtxKey{}, "alice"))
|
|
handled, err := s.resolveHumanDecision(rec, req, core.HITLResponse{CardID: core.ID(first.ID), FreeText: "批准"})
|
|
if !handled || err == nil {
|
|
t.Fatal("ambiguous natural-language approval was accepted")
|
|
}
|
|
}
|