Files
agent-bot 2ad1ef393a
CI / go-vet (pull_request) Successful in 14s
CI / short-tests-report (pull_request) Failing after 36s
CI / backup-restore-drill (pull_request) Successful in 27s
CI / web (pull_request) Successful in 32s
feat(bootstrap): instance identity, single-start lock, root checks (U10c slice 1)
- internal/bootstrap.EnsureInstance on the serve path: normalized writable
  roots, symlink-aware validation, data-root vs vault containment rejection,
  filesystem-root refusal
- instance-marker.json in data root: first boot creates; foreign instance
  ID fails closed (explicit adopt/migrate required, KTD18)
- instance.lock non-blocking flock detects duplicate starts locally without
  any governance tooling; ownership held until process exit
- Config.InstanceID (INSTANCE_ID env/file key; default 'default') joins the
  instance-settings bootstrap denylist (identity cannot be self-overwritten)
- auth is Bearer+localStorage per origin (no cookies) - browser isolation
  holds across ports; documented in the unit template
- deploy/systemd/agent-system@.service template (instance %i: env file,
  DATA_DIR, hardened service scope)
- tests: marker create/foreign reject, vault overlap, invalid IDs, root
  refusal, in-process duplicate-start detection
2026-09-29 08:32:15 +00:00

89 lines
2.8 KiB
Go

package bootstrap
import (
"os"
"path/filepath"
"strings"
"testing"
"chat-agent-system/internal/config"
)
func testCfg(t *testing.T, id, dataDir, vault string) *config.Config {
t.Helper()
return &config.Config{InstanceID: id, DataDir: dataDir, VaultStateRoot: vault}
}
func TestEnsureInstanceCreatesMarkerAndLock(t *testing.T) {
dir := t.TempDir()
cfg := testCfg(t, "alpha", filepath.Join(dir, "alpha"), "")
own, err := EnsureInstance(cfg)
if err != nil {
t.Fatalf("ensure: %v", err)
}
raw, err := os.ReadFile(filepath.Join(dir, "alpha", "instance-marker.json"))
if err != nil {
t.Fatalf("marker missing: %v", err)
}
if !strings.Contains(string(raw), "\"instance_id\": \"alpha\"") {
t.Fatalf("marker content: %s", raw)
}
// 二次取得同实例:同进程内 flock 可重入(同 fd 语义),但模拟误重复
// 启动需要另一个进程;此处验证所有权对象可用并释放。
own.Release()
}
func TestEnsureInstanceRejectsForeignMarker(t *testing.T) {
dir := t.TempDir()
alpha := filepath.Join(dir, "alpha")
if _, err := EnsureInstance(testCfg(t, "alpha", alpha, "")); err != nil {
t.Fatal(err)
}
// 复用同一 data dir 但自称另一实例:必须拒绝(KTD18 不自动认领)
if _, err := EnsureInstance(testCfg(t, "beta", alpha, "")); err == nil {
t.Fatal("foreign instance marker must be rejected")
}
}
func TestEnsureInstanceRejectsVaultOverlap(t *testing.T) {
dir := t.TempDir()
data := filepath.Join(dir, "data")
vault := filepath.Join(data, "vault") // vault 在 data 内:非独立
if _, err := EnsureInstance(testCfg(t, "gamma", data, vault)); err == nil {
t.Fatal("vault inside data dir must be rejected")
}
vaultOut := filepath.Join(dir, "vault")
if _, err := EnsureInstance(testCfg(t, "gamma", data, vaultOut)); err != nil {
t.Fatalf("independent vault should pass: %v", err)
}
}
func TestEnsureInstanceRejectsInvalidID(t *testing.T) {
dir := t.TempDir()
for _, bad := range []string{"", "a/b", "..", "a b", "."} {
if _, err := EnsureInstance(testCfg(t, bad, filepath.Join(dir, "x"), "")); err == nil {
t.Fatalf("invalid instance id %q must be rejected", bad)
}
}
}
func TestEnsureInstanceRejectsRootAsDataDir(t *testing.T) {
if _, err := EnsureInstance(testCfg(t, "root-try", "/", "")); err == nil {
t.Fatal("filesystem root as data dir must be rejected")
}
}
func TestEnsureInstanceDetectsDuplicateStart(t *testing.T) {
dir := t.TempDir()
data := filepath.Join(dir, "dup")
own, err := EnsureInstance(testCfg(t, "dup", data, ""))
if err != nil {
t.Fatalf("first acquire: %v", err)
}
defer own.Release()
// 同机第二次取得(独立 open file description):必须拒绝误重复启动
if _, err := EnsureInstance(testCfg(t, "dup", data, "")); err == nil {
t.Fatal("duplicate start must be detected and rejected")
}
}