package libraryexecutor import ( "context" "encoding/json" "net" "os" "path/filepath" "strings" "testing" "time" ) func shortSocketDir(t *testing.T) string { t.Helper() // unix socket 路径上限 ~104 字节,TMPDIR(本机是 .hermes/cache/scratch)太长, // 测试里用一个短目录并立刻清理。 dir, err := os.MkdirTemp("/tmp", "kgex-") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(dir) }) return dir } // 可用性必须来自运行时观测,而不是"配置里写了 socket 路径"。 func TestProbeReadsIdentityFromTheAgentOverTheSocket(t *testing.T) { root := shortSocketDir(t) socket := filepath.Join(root, "agent.sock") listener, err := net.Listen("unix", socket) if err != nil { t.Fatal(err) } defer listener.Close() go func() { for { conn, acceptErr := listener.Accept() if acceptErr != nil { return } var request map[string]any _ = json.NewDecoder(conn).Decode(&request) payload, _ := json.Marshal(map[string]any{ "project": "kgmaint-weknora", "archive_root": "/tmp/kg-e2e/archive", "journal_root": "/tmp/kg-e2e/journal", "source_app": "kgmaint-app", "source_pg": "kgmaint-postgres", "source_es": "WKNFull-elasticsearch", "writers": "app", "busy": true, "degraded": "", }) _ = json.NewEncoder(conn).Encode(map[string]any{"ok": true, "action": "health", "payload": json.RawMessage(payload)}) conn.Close() } }() observation := Probe(context.Background(), "kgmaint-weknora", socket, 2*time.Second, nil) if !observation.Reachable { t.Fatalf("observation = %+v", observation) } if observation.Identity.Project != "kgmaint-weknora" || observation.Identity.SourceApp != "kgmaint-app" || !observation.Busy { t.Fatalf("identity = %+v", observation.Identity) } if observation.Instance != "kgmaint-weknora" || observation.ObservedAt.IsZero() { t.Fatalf("observation = %+v", observation) } } func TestProbeReportsUnreachableAgentWithoutFakingSuccess(t *testing.T) { observation := Probe(context.Background(), "kgmaint-weknora", filepath.Join(t.TempDir(), "absent.sock"), time.Second, nil) if observation.Reachable || observation.Error == "" { t.Fatalf("observation = %+v", observation) } available, reason := Evaluate(observation, Expectation{Instance: "kgmaint-weknora", Project: "kgmaint-weknora"}, time.Minute, time.Now().UTC()) if available || !strings.Contains(reason, "不可达") { t.Fatalf("available=%v reason=%q", available, reason) } } func TestEvaluateRejectsEveryIdentityAndFreshnessProblem(t *testing.T) { now := time.Date(2026, 10, 29, 8, 0, 0, 0, time.UTC) healthy := Observation{ Instance: "kgmaint-weknora", Reachable: true, ObservedAt: now, Identity: Identity{Project: "kgmaint-weknora", SourceApp: "kgmaint-app"}, } want := Expectation{Instance: "kgmaint-weknora", Project: "kgmaint-weknora"} if available, reason := Evaluate(healthy, want, 2*time.Minute, now); !available || reason != "" { t.Fatalf("healthy executor rejected: %v %q", available, reason) } tests := []struct { name string mut func(*Observation) want func(Expectation) Expectation text string }{ {name: "missing instance config", want: func(Expectation) Expectation { return expectEmptyInstance }, text: "LIBRARY_SOURCE_INSTANCE"}, {name: "row from another instance", mut: func(o *Observation) { o.Instance = "someone-else" }, text: "另一个实例"}, {name: "stale", mut: func(o *Observation) { o.ObservedAt = now.Add(-10 * time.Minute) }, text: "已过期"}, {name: "unreachable", mut: func(o *Observation) { o.Reachable = false; o.Error = "socket missing" }, text: "不可达:socket missing"}, {name: "project not configured", want: func(Expectation) Expectation { return Expectation{Instance: "kgmaint-weknora"} }, text: "LIBRARY_SOURCE_PROJECT"}, {name: "wrong source project", mut: func(o *Observation) { o.Identity.Project = "weknora071full" }, text: "指向的实例不匹配"}, {name: "degraded", mut: func(o *Observation) { o.Degraded = "写入者恢复失败" }, text: "降级状态"}, {name: "busy", mut: func(o *Observation) { o.Busy = true }, text: "正在执行另一个封存"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { observation := healthy if test.mut != nil { test.mut(&observation) } expectation := want if test.want != nil { expectation = test.want(want) } available, reason := Evaluate(observation, expectation, 2*time.Minute, now) if available { t.Fatalf("accepted: %+v", observation) } if !strings.Contains(reason, test.text) { t.Fatalf("reason = %q, want it to mention %q", reason, test.text) } }) } } var expectEmptyInstance = Expectation{Project: "kgmaint-weknora"}