Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements. Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate. Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable. Refs: Versioned dependencies U1/U3; feature/dependency-workspace
78 lines
2.8 KiB
Go
78 lines
2.8 KiB
Go
package store
|
||
|
||
import (
|
||
"path/filepath"
|
||
"testing"
|
||
"time"
|
||
|
||
agentcommand "git-ssh.u1680832.nyat.app/weichen/Agent-Command.git"
|
||
)
|
||
|
||
// 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)
|
||
}
|
||
}
|