Files
Agent-System-Backend/pkg/identity/identity_test.go
T
agent-exec-transport 965eda5786
governance / governance (pull_request) Successful in 15s
ci / vet-and-test (pull_request) Successful in 1m10s
agent-exec transport 4453a2e5e5465aca56a8081251ccd9ca/8b3678a73905
2026-10-19 14:26:29 +00:00

180 lines
5.3 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)
}
if err := os.Chmod(good, 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 := os.Chmod(loose, 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)
}
}