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
127 lines
4.5 KiB
Go
127 lines
4.5 KiB
Go
package store
|
||
|
||
import (
|
||
"testing"
|
||
|
||
core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git"
|
||
)
|
||
|
||
func testSnapshot(surfaceID, owner, plugin string, base int) core.SurfaceSnapshot {
|
||
return core.SurfaceSnapshot{
|
||
Identity: core.SurfaceIdentity{SurfaceID: core.ID(surfaceID), Owner: owner, PluginID: plugin, ConversationID: "c1"},
|
||
BaseRevision: base,
|
||
ActivationEpoch: 1,
|
||
Spec: core.SurfaceSpec{Title: "t", Nodes: []core.UINode{{Type: core.UIMarkdown, ID: "n1", Markdown: "x"}}},
|
||
}
|
||
}
|
||
|
||
func TestSurfaceSaveCASAndRead(t *testing.T) {
|
||
st := openTestStore(t)
|
||
// 新建 → revision 1
|
||
snap, err := st.SaveSurfaceSnapshot(testSnapshot("s1", "alice", "genui", 0))
|
||
if err != nil || snap.Revision != 1 {
|
||
t.Fatalf("create: rev=%d err=%v", snap.Revision, err)
|
||
}
|
||
// 基于最新版的更新 → revision 2
|
||
snap2, err := st.SaveSurfaceSnapshot(testSnapshot("s1", "alice", "genui", 1))
|
||
if err != nil || snap2.Revision != 2 {
|
||
t.Fatalf("update: rev=%d err=%v", snap2.Revision, err)
|
||
}
|
||
// 过期 base → 冲突并携带当前版本
|
||
_, err = st.SaveSurfaceSnapshot(testSnapshot("s1", "alice", "genui", 1))
|
||
var conflict *ErrRevisionConflict
|
||
if err == nil {
|
||
t.Fatal("stale base should conflict")
|
||
}
|
||
if e, ok := err.(*ErrRevisionConflict); !ok {
|
||
t.Fatalf("want ErrRevisionConflict, got %v", err)
|
||
} else if e.Current != 2 {
|
||
t.Fatalf("conflict should carry current=2, got %d", e.Current)
|
||
}
|
||
_ = conflict
|
||
// owner 隔离:bob 读不到 alice 的页面
|
||
if _, err := st.GetSurfaceSnapshot("bob", "s1", 0); err != ErrNotFound {
|
||
t.Fatalf("cross-owner read should be NotFound, got %v", err)
|
||
}
|
||
// 跨 owner 写同 id → 冲突(surface 已存在且非本 owner 视为冲突路径)
|
||
if _, err := st.SaveSurfaceSnapshot(testSnapshot("s1", "bob", "genui", 0)); err == nil {
|
||
t.Fatal("cross-owner create on existing id should fail")
|
||
}
|
||
// 读取往返:字段保真
|
||
got, err := st.GetSurfaceSnapshot("alice", "s1", 0)
|
||
if err != nil || got.Revision != 2 || got.Spec.Title != "t" || len(got.Spec.Nodes) != 1 {
|
||
t.Fatalf("read-back mismatch: %+v err=%v", got, err)
|
||
}
|
||
old, err := st.GetSurfaceSnapshot("alice", "s1", 1)
|
||
if err != nil || old.Revision != 1 {
|
||
t.Fatalf("historical revision read failed: %+v err=%v", old, err)
|
||
}
|
||
}
|
||
|
||
func TestSurfaceActivationEpoch(t *testing.T) {
|
||
st := openTestStore(t)
|
||
// 无行 = 默认关闭(KTD7:不沿用 grants 缺省 true)
|
||
a, err := st.GetActivation("alice", "genui")
|
||
if err != nil || a.Enabled || a.Epoch != 0 {
|
||
t.Fatalf("default should be disabled/epoch0: %+v err=%v", a, err)
|
||
}
|
||
a1, err := st.SetActivation("alice", "genui", true)
|
||
if err != nil || !a1.Enabled || a1.Epoch != 1 {
|
||
t.Fatalf("enable: %+v err=%v", a1, err)
|
||
}
|
||
a2, err := st.SetActivation("alice", "genui", false)
|
||
if err != nil || a2.Enabled || a2.Epoch != 2 {
|
||
t.Fatalf("disable should bump epoch: %+v err=%v", a2, err)
|
||
}
|
||
a3, err := st.SetActivation("alice", "genui", true)
|
||
if err != nil || a3.Epoch != 3 {
|
||
t.Fatalf("re-enable should bump epoch: %+v err=%v", a3, err)
|
||
}
|
||
// owner 隔离
|
||
b, _ := st.GetActivation("bob", "genui")
|
||
if b.Enabled || b.Epoch != 0 {
|
||
t.Fatalf("bob activation leaked: %+v", b)
|
||
}
|
||
}
|
||
|
||
func TestSurfacePendingEvents(t *testing.T) {
|
||
st := openTestStore(t)
|
||
if _, err := st.SaveSurfaceSnapshot(testSnapshot("s2", "alice", "genui", 0)); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := st.SetSurfaceEventPending("alice", "s2", 1, true); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
pend, err := st.ListSurfaceEventsPending(10)
|
||
if err != nil || len(pend) != 1 || pend[0].SurfaceID != "s2" || pend[0].Revision != 1 {
|
||
t.Fatalf("pending scan: %+v err=%v", pend, err)
|
||
}
|
||
// owner 校验:他人不能标记/清除
|
||
if err := st.SetSurfaceEventPending("bob", "s2", 1, false); err != ErrNotFound {
|
||
t.Fatalf("cross-owner pending clear should be NotFound, got %v", err)
|
||
}
|
||
if err := st.SetSurfaceEventPending("alice", "s2", 1, false); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if pend, _ := st.ListSurfaceEventsPending(10); len(pend) != 0 {
|
||
t.Fatalf("pending should be cleared: %+v", pend)
|
||
}
|
||
// 列表投影(含 title 解析)
|
||
if _, err := st.SaveSurfaceSnapshot(testSnapshot("s3", "alice", "genui", 0)); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
metas, err := st.ListSurfaces("alice", "c1")
|
||
if err != nil || len(metas) != 2 {
|
||
t.Fatalf("list: %+v err=%v", metas, err)
|
||
}
|
||
for _, m := range metas {
|
||
if m.Title != "t" {
|
||
t.Fatalf("meta title not parsed: %+v", m)
|
||
}
|
||
}
|
||
other, _ := st.ListSurfaces("alice", "c-other")
|
||
if len(other) != 0 {
|
||
t.Fatalf("conversation filter broken: %+v", other)
|
||
}
|
||
}
|