Files
Agent-System-Backend/pkg/identity/identity_test.go
T
weichenandCursor 0f192a4813
ci / vet-and-test (pull_request) Successful in 57s
Merge origin/main into feature/d1-identity-open
Why: main gained AGENT_SYSTEM_DSN fallback in #182 after this branch
was cut. Both changes touch identity DSN parsing.
Evidence: go test ./pkg/identity/ ./internal/store/ ./cmd/agent-system/ for the identity, schema, and unknown-subcommand cases passed after the resolution.
Impact: keeps open-existing identity mode and the 0600 dsn file. Environment
DSN still wins when --dsn is absent, via ResolveDSN.
Refs: Backend #182; recovery plan r3.1 D1

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 14:55:56 +00:00

174 lines
5.2 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 identity
import (
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
"testing"
"agent-system-backend/internal/store"
)
// capture 把 stdout/stderr 重定向到管道并在 fn 返回后给出捕获文本。
func capture(t *testing.T, fn func()) (stdout, stderr string) {
t.Helper()
oldOut, oldErr := os.Stdout, os.Stderr
outR, outW, err1 := os.Pipe()
errR, errW, err2 := os.Pipe()
if err1 != nil || err2 != nil {
t.Fatalf("pipe: %v %v", err1, err2)
}
os.Stdout, os.Stderr = outW, errW
defer func() { os.Stdout, os.Stderr = oldOut, oldErr }()
done := make(chan struct{})
go func() {
defer close(done)
bo, _ := io.ReadAll(outR)
be, _ := io.ReadAll(errR)
stdout, stderr = string(bo), string(be)
}()
fn()
outW.Close()
errW.Close()
<-done
return stdout, stderr
}
// 探测契约(svc.sh svc_bin_has_identity):无参输出 usage 形态——同时含
// "identity" 与 "ensure",无服务态痕迹;未知子命令同样不落服务路径。
func TestUsageAndUnknownExitWithoutServe(t *testing.T) {
out, errS := capture(t, func() { Run(nil) })
_ = out
if !strings.Contains(errS, "identity") || !strings.Contains(errS, "ensure") {
t.Fatalf("usage 须同时含 identity 与 ensure:%q", errS)
}
if rc := Run(nil); rc != 2 {
t.Fatalf("无参 rc=%d,期望 2", rc)
}
if rc := Run([]string{"bogus", "--instance-id", "x"}); rc != 2 {
t.Fatalf("未知子命令 rc=%d,期望 2", rc)
}
}
func TestCapabilities(t *testing.T) {
out, _ := capture(t, func() { Run([]string{"capabilities"}) })
var doc Document
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &doc); err != nil {
t.Fatalf("capabilities 不是 JSON:%v %q", err, out)
}
if doc.Protocol != 1 || doc.Identity.Open != "existing-no-migrate" || doc.CLI.UnknownSubcommand != "reject" {
t.Fatalf("能力文档异常:%+v", doc)
}
if doc.Schema == nil || doc.Schema.Target != store.SchemaTarget() || doc.Schema.CompatFloor != store.SchemaCompatFloor() {
t.Fatalf("schema 能力异常:%+v", doc.Schema)
}
}
func migratedData(t *testing.T) (string, int) {
t.Helper()
dir := t.TempDir()
data := filepath.Join(dir, "data")
st, err := store.OpenSQLite(filepath.Join(data, "agent-system.db"))
if err != nil {
t.Fatal(err)
}
ver, err := st.SchemaVersion()
if err != nil {
t.Fatal(err)
}
st.Close()
return data, ver
}
func TestEnsureVerifyRoundTripSQLite(t *testing.T) {
data, before := migratedData(t)
args := []string{"--data", data}
id := "svc-t1-abcdef123456"
if rc := Run(append([]string{"ensure", "--instance-id", id}, args...)); rc != 0 {
t.Fatalf("ensure rc=%d", rc)
}
out, _ := capture(t, func() {
if rc := Run(append([]string{"verify", "--instance-id", id}, args...)); rc != 0 {
t.Fatalf("verify rc=%d", rc)
}
})
if !strings.Contains(out, "schema_version=") {
t.Fatalf("verify 未带 schema_version:%q", out)
}
st, err := store.OpenIdentity("", data)
if err != nil {
t.Fatal(err)
}
defer st.Close()
after, err := st.SchemaVersion()
if err != nil {
t.Fatal(err)
}
if after != before {
t.Fatalf("身份命令改变了 schema:%d → %d", before, after)
}
if rc := Run(append([]string{"verify", "--instance-id", "svc-t1-zzzzzz999999"}, args...)); rc != 1 {
t.Fatalf("错 id 须 rc=1,得 %d", rc)
}
if rc := Run(append([]string{"verify", "--instance-id", id, "--dbref", "pg"}, args...)); rc != 1 {
t.Fatalf("错 dbref 须 rc=1,得 %d", rc)
}
}
func TestVerifyMissingDBCreatesNothing(t *testing.T) {
dir := t.TempDir()
missing := filepath.Join(dir, "absent")
if rc := Run([]string{"verify", "--instance-id", "svc-missing-aaaa", "--data", missing}); rc == 0 {
t.Fatal("缺库不应成功")
}
if _, err := os.Stat(missing); !os.IsNotExist(err) {
t.Fatalf("缺库路径被创建:%v", err)
}
if _, err := os.Stat(filepath.Join(missing, "agent-system.db")); !os.IsNotExist(err) {
t.Fatal("缺库时创建了数据库文件")
}
}
func TestDSNFileMode(t *testing.T) {
dir := t.TempDir()
good := filepath.Join(dir, "dsn")
if err := os.WriteFile(good, []byte("postgres://example\n"), 0o600); err != nil {
t.Fatal(err)
}
got, err := ResolveDSN("", good)
if err != nil || got != "postgres://example" {
t.Fatalf("0600 文件:%q %v", got, err)
}
loose := filepath.Join(dir, "loose")
if err := os.WriteFile(loose, []byte("postgres://x"), 0o644); err != nil {
t.Fatal(err)
}
if _, err := ResolveDSN("", loose); err == nil {
t.Fatal("0644 的 dsn 文件必须拒绝")
}
_, errS := capture(t, func() { ResolveDSN("postgres://flag", "") })
if !strings.Contains(errS, "警告") {
t.Fatalf("--dsn 应告警:%q", errS)
}
}
// DSN 交付纪律:空的 AGENT_SYSTEM_DSN 不干扰已有 SQLite 库。
func TestDSNEnvFallback(t *testing.T) {
t.Setenv("AGENT_SYSTEM_DSN", "")
data, _ := migratedData(t)
id := "svc-dsnenv-abcdef123456"
if rc := Run([]string{"ensure", "--instance-id", id, "--data", data}); rc != 0 {
t.Fatalf("ensure rc=%d", rc)
}
if rc := Run([]string{"verify", "--instance-id", id, "--data", data}); rc != 0 {
t.Fatalf("verify rc=%d", rc)
}
t.Setenv("AGENT_SYSTEM_DSN", "postgres://from-env")
got, err := ResolveDSN("", "")
if err != nil || got != "postgres://from-env" {
t.Fatalf("环境 DSN:%q %v", got, err)
}
}