Files
Agent-System-Backend/internal/store/outbox_tx_test.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
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
2026-10-13 15:47:16 +00:00

101 lines
3.1 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 (
"database/sql"
"errors"
"testing"
"time"
corepkg "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
)
// U5 余项(KTD5):同事务 outbox——业务提交与命令投递原子(提交即生效、
// 回滚即同灭);KTD6 前半——事件提交与 NOTIFY 同事务(通知不早于事件可见)。
func TestEnqueueCommandInTxAtomicWithBusiness(t *testing.T) {
st := openTestStore(t)
// 业务事务回滚 → 命令随之消失。
tx, err := st.db.Begin()
if err != nil {
t.Fatal(err)
}
if err := st.setSettingsInTx(tx, "owner_tx", "scope_tx", `{"a":1}`); err != nil {
t.Fatal(err)
}
if err := st.EnqueueCommandInTx(tx, "cmd_tx_1", "cancel", "run_tx", "cnv_tx", 0, map[string]string{"o": "owner_tx"}); err != nil {
t.Fatal(err)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
if _, _, err := st.CommandState("cmd_tx_1"); !errors.Is(err, ErrNotFound) {
t.Fatalf("回滚后命令应消失:%v", err)
}
// 提交 → 业务与命令同时可见。
tx, err = st.db.Begin()
if err != nil {
t.Fatal(err)
}
if err := st.setSettingsInTx(tx, "owner_tx", "scope_tx", `{"a":1}`); err != nil {
t.Fatal(err)
}
if err := st.EnqueueCommandInTx(tx, "cmd_tx_2", "hitl", "run_tx", "cnv_tx", 0, nil); err != nil {
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
state, _, err := st.CommandState("cmd_tx_2")
if err != nil || state != "pending" {
t.Fatalf("提交后命令应 pending:state=%s err=%v", state, err)
}
// 幂等:同 id 重复投递(同事务)不报错不增行。
tx, _ = st.db.Begin()
if err := st.EnqueueCommandInTx(tx, "cmd_tx_2", "hitl", "run_tx", "cnv_tx", 0, nil); err != nil {
t.Fatal(err)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
got, err := st.ClaimPendingCommands("instX", 16)
if err != nil {
t.Fatal(err)
}
n := 0
for _, c := range got {
if c.CommandID == "cmd_tx_2" {
n++
}
}
if n != 1 {
t.Fatalf("幂等后应仍只 1 条:%d", n)
}
}
// setSettingsInTx 测试辅助:在既有事务里写一行设置(代表业务提交)。
func (s *Store) setSettingsInTx(tx *sql.Tx, owner, scope, vals string) error {
_, err := tx.Exec(s.q(`INSERT INTO settings (owner, scope, "values") VALUES (?,?,?)
ON CONFLICT(owner, scope) DO UPDATE SET "values"=excluded."values"`), owner, scope, vals)
return err
}
// 事件提交与 NOTIFY 同事务:AppendEvents 后事件与通知原子可见
// (KTD6:通知不早于事件;SQLite 档无 NOTIFY,验证事件落库即可)。
func TestAppendEventsCommitsWithNotify(t *testing.T) {
st := openTestStore(t)
core := []corepkg.StreamEvent{}
for i := 0; i < 3; i++ {
core = append(core, corepkg.StreamEvent{ConversationID: "cnv_n", RunID: "r", TS: time.Now().UTC().Format(time.RFC3339Nano), V: 1, Type: corepkg.EvRunPhase, Payload: []byte(`{}`)})
}
if _, err := st.AppendEvents("cnv_n", core); err != nil {
t.Fatal(err)
}
var after int64
if err := st.db.QueryRow(`SELECT COALESCE(MAX(seq),0) FROM events WHERE conversation_id='cnv_n'`).Scan(&after); err != nil {
t.Fatal(err)
}
if after != 3 {
t.Fatalf("事件应 +3:%d", after)
}
}