Why: Downstream products must extend the base through a public package and host API instead of editing internal code. Evidence: go test ./... passed, including plugin v2 assets, sidecar startup, migration v13, pending confirms, HITL timeout, hash embedder, command pairs, and the extension surface snapshot. Impact: apiVersion 2.0-experimental rejects empty network allowlists for declarative HTTP. Hash embeddings stay opt-in via models.embedding.provider=hash. Plugin secrets resolve per owner at execution time. Refs: docs/proposals/2026-10-01-plugin-contract-v2.md Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
2.9 KiB
Go
98 lines
2.9 KiB
Go
package store
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
func TestPlatformTables(t *testing.T) {
|
|
st, err := OpenSQLite(filepath.Join(t.TempDir(), "p.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { st.Close() })
|
|
|
|
if err := st.SetPluginEnabled("acme", "rdm", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
on, err := st.PluginEnabled("acme", "rdm")
|
|
if err != nil || on {
|
|
t.Fatalf("disabled grant: on=%v err=%v", on, err)
|
|
}
|
|
on, err = st.PluginEnabled("acme", "other")
|
|
if err != nil || !on {
|
|
t.Fatalf("missing grant defaults to enabled: on=%v err=%v", on, err)
|
|
}
|
|
|
|
if err := st.SetPluginSchemaVersion("rdm", 2); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if v, err := st.PluginSchemaVersion("rdm"); err != nil || v != 2 {
|
|
t.Fatalf("schema: %d %v", v, err)
|
|
}
|
|
if err := st.PutPluginKV("rdm", "acme", "slice", "s1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, ok, err := st.GetPluginKV("rdm", "acme", "slice")
|
|
if err != nil || !ok || got != "s1" {
|
|
t.Fatalf("kv: %q ok=%v err=%v", got, ok, err)
|
|
}
|
|
|
|
task, err := st.CreateBackgroundTask("acme", "ingest", "入库", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if task.ConversationID != "" || task.Status != "queued" {
|
|
t.Fatalf("task should not require a conversation: %+v", task)
|
|
}
|
|
if err := st.UpdateBackgroundTask("acme", task.ID, "done", 100, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
task, err = st.GetBackgroundTask("acme", task.ID)
|
|
if err != nil || task.Status != "done" || task.Progress != 100 {
|
|
t.Fatalf("updated: %+v %v", task, err)
|
|
}
|
|
|
|
exp := time.Now().UTC().Add(time.Hour)
|
|
mem, err := st.PutMemory("acme", core.MemoryEntry{Scope: "user", Type: "preference", Content: "用中文", Source: "manual", ExpiresAt: &exp})
|
|
if err != nil || mem.ID == "" {
|
|
t.Fatal(err)
|
|
}
|
|
list, err := st.ListMemory("acme", "user", "")
|
|
if err != nil || len(list) != 1 || list[0].Content != "用中文" || list[0].ExpiresAt == nil {
|
|
t.Fatalf("memory: %+v %v", list, err)
|
|
}
|
|
|
|
if err := st.AppendAuditEvent(AuditEvent{Owner: "acme", Action: "upload", Target: "f", Evidence: `{"ok":true}`, TraceID: "tr"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
evs, err := st.ListAuditEvents("acme", 10)
|
|
if err != nil || len(evs) != 1 || evs[0].Action != "upload" || evs[0].TraceID != "tr" {
|
|
t.Fatalf("audit: %+v %v", evs, err)
|
|
}
|
|
|
|
if err := st.SavePendingConfirm("card1", "acme", `{"id":"x"}`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
owner, payload, ok, err := st.TakePendingConfirm("card1")
|
|
if err != nil || !ok || owner != "acme" || payload != `{"id":"x"}` {
|
|
t.Fatalf("take: %s %s %v %v", owner, payload, ok, err)
|
|
}
|
|
_, _, ok, err = st.TakePendingConfirm("card1")
|
|
if err != nil || ok {
|
|
t.Fatalf("second take must miss: ok=%v err=%v", ok, err)
|
|
}
|
|
|
|
st1, err := st.AccountTokens("acme", 30, 40)
|
|
if err != nil || st1.Total != 30 || st1.Over {
|
|
t.Fatalf("under budget: %+v %v", st1, err)
|
|
}
|
|
st2, err := st.AccountTokens("acme", 20, 40)
|
|
if err != nil || st2.Total != 50 || !st2.Over {
|
|
t.Fatalf("over budget: %+v %v", st2, err)
|
|
}
|
|
}
|