Files
Agent-System-Backend/internal/store/audit_test.go
T
weichen e0b1c27f1f
ci / vet-and-test (pull_request) Successful in 42s
Scope command audit storage and queries to the requesting owner
Why: GET /api/commands/audit leaked every tenant's audit rows (command ids, entrances, conversation ids, param digests) to any authenticated user; owner was never recorded nor filtered.
Evidence: Red run at a7cc69b FAIL (alice sees bob's row); after fix go test ./... 32/32 packages ok, including TestCommandAuditOwnerScoping and the v18 backfill test (rows recorded on the task card).
Impact: v18 migration adds command_audit.owner with actor-based backfill (empty-actor rows stay invisible via API, kept in-table); AppendAudit/ListAudits gain owner params; cmdbus port and fakes updated; handler passes authenticated owner. No public extension surface change.
Refs: Task card R1; U0 proposal section 5 C7 finding.
2026-10-08 14:02:19 +00:00

78 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"path/filepath"
"testing"
"time"
agentcommand "agent-command"
)
// v18 迁移:command_audit 增加 owner 列并按 payload.actor 回填存量行
// (GET /api/commands/audit 曾跨租户可读;见 migrate.go v18 注释)。
// 路径:全新库(已到 v18)→ MigrateDown(17) 回出无 owner 列的旧表 →
// 直插两行修复前形态的信封 → 重开触发 v18 → 归属断言。
func TestCommandAuditOwnerBackfill(t *testing.T) {
path := filepath.Join(t.TempDir(), "audit.db")
st, err := OpenSQLite(path)
if err != nil {
t.Fatal(err)
}
if err := st.MigrateDown(17); err != nil {
t.Fatalf("migrate down to v17: %v", err)
}
now := time.Now().UTC().Format(time.RFC3339Nano)
legacy := func(actor, cmd string) string {
// 修复前形态:信封 Actor 即发起租户,表内无归属列。
return `{"time":"` + now + `","actor":"` + actor + `","commandId":"` + cmd + `","source":"builtin","state":"committed"}`
}
if _, err := st.db.Exec(st.q(`INSERT INTO command_audit (time, command_id, source, entrance, state, operation_id, conversation_id, payload)
VALUES (?, 'cmd.a', 'builtin', 'api', 'committed', '', 'c1', ?)`), now, legacy("alice", "cmd.a")); err != nil {
t.Fatal(err)
}
if _, err := st.db.Exec(st.q(`INSERT INTO command_audit (time, command_id, source, entrance, state, operation_id, conversation_id, payload)
VALUES (?, 'cmd.x', 'builtin', 'api', 'committed', '', '', ?)`), now, `{"time":"`+now+`","commandId":"cmd.x","state":"committed"}`); err != nil {
t.Fatal(err) // actor 为空的存量行:无法归属
}
if err := st.Close(); err != nil {
t.Fatal(err)
}
st2, err := OpenSQLite(path) // 重开触发 v18:加列 + 回填
if err != nil {
t.Fatal(err)
}
defer st2.Close()
aliceRows, err := st2.ListAudits("alice", "", 10)
if err != nil {
t.Fatal(err)
}
if len(aliceRows) != 1 || aliceRows[0].CommandID != "cmd.a" {
t.Fatalf("alice rows = %+v, want 1 条 cmd.a(actor 回填归属)", aliceRows)
}
bobRows, err := st2.ListAudits("bob", "", 10)
if err != nil {
t.Fatal(err)
}
if len(bobRows) != 0 {
t.Fatalf("bob rows = %+v, want 0", bobRows)
}
if rows, _ := st2.ListAudits("", "", 10); len(rows) != 0 {
t.Fatalf("空 owner(actor 缺失的存量行)不得经 API 归属返回,got %d 行", len(rows))
}
// 新写入走 owner 列;同租户按会话过滤仍可用。
if err := st2.AppendAudit("alice", agentcommand.AuditEnvelope{CommandID: "cmd.a2", Actor: "alice",
State: agentcommand.StateCommitted, ConversationID: "c2"}); err != nil {
t.Fatal(err)
}
convRows, err := st2.ListAudits("alice", "c2", 10)
if err != nil {
t.Fatal(err)
}
if len(convRows) != 1 || convRows[0].CommandID != "cmd.a2" {
t.Fatalf("alice/c2 rows = %+v, want cmd.a2", convRows)
}
}