Files
Agent-System-Backend/pkg/identity/identity_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

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"
"git-ssh.u1680832.nyat.app/weichen/Agent-System-Backend.git/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)
}
}