Why: F10 - any handler error became a no-side-effect rejected receipt and an audit-store failure dropped the generated operation ID, losing the reconciliation handle and inviting unsafe retries. Evidence: cmdbus tests pass (verify-u7b.json); post-execution-unknown, audit-failure-keeps-ID and confirmation-idempotency cases added. Impact: unknown receipts now carry the trusted operation ID via the U7A constructor; rejected stays ID-free; no auto-replay anywhere. Refs: feature/agent-stack-audit-remediation-b1 U7B
68 lines
2.5 KiB
Go
68 lines
2.5 KiB
Go
package cmdbus
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
agentcommand "agent-command"
|
|
"agent-system-backend/internal/store"
|
|
)
|
|
|
|
func TestPendingConfirmRestoredAfterRestart(t *testing.T) {
|
|
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "c.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
cards := &memCards{}
|
|
b := newTestBus(&memAudit{}, cards)
|
|
b.SetPendingStore(st)
|
|
resp := b.Dispatch(context.Background(), Request{
|
|
ID: "demo.risky", Params: map[string]any{"target": "prod"}, Entrance: "button", ConversationID: "cnv_1", Owner: "acme",
|
|
})
|
|
if resp.Card == nil {
|
|
t.Fatal("expected confirm card")
|
|
}
|
|
// 模拟重启:丢掉进程内 map,只留存储。
|
|
b.pending = map[string]pending{}
|
|
again := b.Confirm("acme", resp.Card.ID, "confirm")
|
|
if again.Receipt.State != agentcommand.StateCommitted {
|
|
t.Fatalf("restored confirm: %+v", again.Receipt)
|
|
}
|
|
}
|
|
|
|
// failingPendingStore 是"确认卡已创建、挂起态写不进去"的替身:受理已经发生
|
|
// (卡在收件箱里),只是结果不可知。
|
|
type failingPendingStore struct{ err error }
|
|
|
|
func (p *failingPendingStore) SavePendingConfirm(string, string, string) error { return p.err }
|
|
func (p *failingPendingStore) TakePendingConfirm(string) (string, string, bool, error) {
|
|
return "", "", false, p.err
|
|
}
|
|
|
|
// TestConfirmPendingWriteFailureKeepsCardOperationID:挂起态落盘失败不得吞掉操作
|
|
// 标识——卡 ID 就是本次受理的句柄,unknown 必须带出去(否则受理事实无凭证)。
|
|
func TestConfirmPendingWriteFailureKeepsCardOperationID(t *testing.T) {
|
|
audit, cards := &memAudit{}, &memCards{}
|
|
b := newTestBus(audit, cards)
|
|
b.SetPendingStore(&failingPendingStore{err: errors.New("pending store down")})
|
|
resp := b.Dispatch(context.Background(), Request{
|
|
ID: "demo.risky", Params: map[string]any{"target": "prod"}, ConversationID: "cnv_1", Owner: "alice",
|
|
})
|
|
if resp.Receipt.State != agentcommand.StateUnknown || !strings.Contains(resp.Receipt.Reason, "confirm pending") {
|
|
t.Fatalf("pending write failure: %+v", resp.Receipt)
|
|
}
|
|
if resp.Receipt.OperationID != "card_x1" || resp.Receipt.Validate() != nil {
|
|
t.Fatalf("pending write failure dropped the card operation handle: %+v", resp.Receipt)
|
|
}
|
|
if len(cards.created) != 1 || len(b.pending) != 0 {
|
|
t.Fatalf("card created=%d pending=%d", len(cards.created), len(b.pending))
|
|
}
|
|
if len(audit.logs) != 1 || audit.logs[0].State != agentcommand.StateUnknown || audit.logs[0].OperationID != "card_x1" {
|
|
t.Fatalf("audit envelope: %+v", audit.logs)
|
|
}
|
|
}
|