diff --git a/internal/server/audit_scope_test.go b/internal/server/audit_scope_test.go new file mode 100644 index 0000000..b90d3e5 --- /dev/null +++ b/internal/server/audit_scope_test.go @@ -0,0 +1,68 @@ +package server + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + agentcommand "agent-command" +) + +// 命令审计按租户作用域(回归:GET /api/commands/audit 曾返回全租户审计行—— +// ListAudits 无 owner 条件、command_audit 无 owner 列;多用户档下任何登录 +// 用户可读他人命令 ID、入口、会话 ID 与参数摘要)。 +// 经真实认证中间件(API key → owner)请求,断言每个租户只见本租户的行。 +func TestCommandAuditOwnerScoping(t *testing.T) { + srv, st := newAuthTestServer(t) + h := srv.Handler() + + aliceKey, err := st.CreateAPIKey("alice", "e2e") + if err != nil { + t.Fatal(err) + } + bobKey, err := st.CreateAPIKey("bob", "e2e") + if err != nil { + t.Fatal(err) + } + + // 两租户各产生一条命令审计(信封 Actor=发起租户——修复前唯一可用的归属线索)。 + if err := st.AppendAudit(agentcommand.AuditEnvelope{ + CommandID: "cmd.alice", Actor: "alice", State: agentcommand.StateCommitted, + }); err != nil { + t.Fatal(err) + } + if err := st.AppendAudit(agentcommand.AuditEnvelope{ + CommandID: "cmd.bob", Actor: "bob", State: agentcommand.StateCommitted, + }); err != nil { + t.Fatal(err) + } + + get := func(key string) []map[string]any { + t.Helper() + req := httptest.NewRequest(http.MethodGet, "/api/commands/audit", nil) + req.Header.Set("Authorization", "Bearer "+key) + rec := httptest.NewRecorder() + h.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("audit query: %d %s", rec.Code, rec.Body.String()) + } + var out struct { + Audits []map[string]any `json:"audits"` + } + if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil { + t.Fatal(err) + } + return out.Audits + } + + for key, owner := range map[string]string{aliceKey: "alice", bobKey: "bob"} { + rows := get(key) + if len(rows) != 1 { + t.Fatalf("owner %s: audit rows = %d, want 1(跨租户行不得返回;got=%v)", owner, len(rows), rows) + } + if rows[0]["actor"] != owner || rows[0]["commandId"] != "cmd."+owner { + t.Fatalf("owner %s: wrong row %v", owner, rows[0]) + } + } +}