yao/tai/tai_test.go
Max 7cccf62841 feat(tai): refactor Dial* functions and remove requireKubeConfig hard-fail
- Consolidate DialRemote/DialTunnel common logic into buildResources + dialEnv interface
- Remove strict capability check that prevented ConnResources creation for host-exec-only nodes
- Merge gRPC-discovered capabilities with registration-declared capabilities in DialTunnel
- Replace requireKubeConfig hard-fail with graceful skip when kubeconfig is absent
- Introduce tai/types package for shared Ports/Capabilities/SystemInfo/AuthInfo/NodeMeta
- Add tai/conn.go (ConnResources) and tai/dial.go (DialRemote/DialTunnel/DialLocal)
- Rename tai/sandbox → tai/runtime for clarity
- Update sandbox/v2, workspace, agent/sandbox/v2 test utilities for build-tag isolation

Made-with: Cursor
2026-03-12 15:44:24 +08:00

239 lines
5.2 KiB
Go

package tai
import (
"os"
"strconv"
"testing"
"github.com/yaoapp/yao/tai/registry"
"github.com/yaoapp/yao/tai/types"
"github.com/yaoapp/yao/tai/volume"
)
func taiTestHost() string {
if h := os.Getenv("TAI_TEST_HOST"); h != "" {
return h
}
return "127.0.0.1"
}
func taiTestPorts() Ports {
return Ports{
Docker: envPort("TAI_TEST_DOCKER_PORT", 0),
HTTP: envPort("TAI_TEST_HTTP_PORT", 0),
VNC: envPort("TAI_TEST_VNC_PORT", 0),
}
}
func envPort(key string, fallback int) int {
if v := os.Getenv(key); v != "" {
if p, err := strconv.Atoi(v); err == nil {
return p
}
}
return fallback
}
func TestMergedPorts(t *testing.T) {
p := mergedPorts(Ports{HTTP: 8888})
if p.HTTP != 8888 {
t.Errorf("HTTP = %d, want 8888", p.HTTP)
}
if p.GRPC != 19100 {
t.Errorf("GRPC = %d, want 19100 (default)", p.GRPC)
}
if p.VNC != 16080 {
t.Errorf("VNC = %d, want 16080 (default)", p.VNC)
}
if p.Docker != 0 {
t.Errorf("Docker = %d, want 0 (unset)", p.Docker)
}
if p.K8s != 0 {
t.Errorf("K8s = %d, want 0 (unset)", p.K8s)
}
}
func TestMergedPortsAll(t *testing.T) {
p := mergedPorts(Ports{GRPC: 1, HTTP: 2, VNC: 3, Docker: 4, K8s: 5})
if p.GRPC != 1 || p.HTTP != 2 || p.VNC != 3 || p.Docker != 4 || p.K8s != 5 {
t.Errorf("unexpected ports: %+v", p)
}
}
func TestDialLocalSuccess(t *testing.T) {
res, err := DialLocal("", t.TempDir(), nil)
if err != nil {
t.Skipf("Docker not available: %v", err)
}
defer res.Close()
if res.Volume == nil {
t.Error("Volume should not be nil")
}
if res.Runtime == nil {
t.Error("Runtime should not be nil")
}
}
func TestDialLocalWithVolume(t *testing.T) {
dir := t.TempDir()
vol := volume.NewLocal(dir)
res, err := DialLocal("", dir, vol)
if err != nil {
t.Skipf("Docker not available: %v", err)
}
defer res.Close()
if res.DataDir != dir {
t.Errorf("DataDir = %q, want %q", res.DataDir, dir)
}
if res.Volume == nil {
t.Error("Volume should not be nil")
}
}
func TestDialLocalExplicitSocket(t *testing.T) {
res, err := DialLocal("unix:///var/run/docker.sock", t.TempDir(), nil)
if err != nil {
t.Skipf("Docker not available: %v", err)
}
defer res.Close()
if res.Runtime == nil {
t.Error("Runtime should not be nil for explicit unix socket")
}
}
func TestDialRemoteDocker(t *testing.T) {
host := taiTestHost()
grpcPort := envPort("TAI_TEST_GRPC_PORT", 19100)
ports := taiTestPorts()
ports.GRPC = grpcPort
res, err := DialRemote(host, ports)
if err != nil {
t.Skipf("Tai not available at %s:%d: %v", host, grpcPort, err)
}
defer res.Close()
t.Logf("remote docker: host=%s ports=%+v", host, res.Ports)
if res.Volume == nil {
t.Error("Volume should not be nil")
}
if res.Runtime == nil {
t.Error("Runtime should not be nil")
}
}
func TestDialRemoteK8s(t *testing.T) {
host := os.Getenv("TAI_TEST_K8S_HOST")
kubeconfig := os.Getenv("TAI_TEST_KUBECONFIG")
if host == "" || kubeconfig == "" {
t.Skip("TAI_TEST_K8S_HOST or TAI_TEST_KUBECONFIG not set")
}
grpcPort := envPort("TAI_TEST_K8S_GRPC_PORT", envPort("TAI_TEST_GRPC_PORT", 19100))
ports := Ports{
K8s: envPort("TAI_TEST_K8S_PORT", 16443),
GRPC: grpcPort,
HTTP: envPort("TAI_TEST_K8S_HTTP_PORT", 8099),
VNC: envPort("TAI_TEST_K8S_VNC_PORT", 16080),
}
res, err := DialRemote(host, ports,
WithDialRuntime(types.K8s),
WithDialKubeConfig(kubeconfig),
WithDialNamespace("default"),
)
if err != nil {
t.Skipf("Tai K8s not available: %v", err)
}
defer res.Close()
if res.Runtime == nil {
t.Error("Runtime should not be nil")
}
}
func TestDialRemoteK8sMissingKubeConfig(t *testing.T) {
host := taiTestHost()
grpcPort := envPort("TAI_TEST_GRPC_PORT", 19100)
_, err := DialRemote(host, Ports{GRPC: grpcPort}, WithDialRuntime(types.K8s))
if err == nil {
t.Skip("Tai happened to be reachable; test only valid when gRPC is up")
}
}
func TestRegisterLocal(t *testing.T) {
registry.Init(nil)
reg := registry.Global()
dir := t.TempDir()
ok := RegisterLocal(WithDataDir(dir))
if !ok {
t.Skip("Docker not available, skipping RegisterLocal test")
}
meta, found := reg.Get("local")
if !found {
t.Fatal("expected 'local' node in registry after RegisterLocal")
}
if meta.Mode != "local" {
t.Errorf("mode = %q, want 'local'", meta.Mode)
}
if meta.Status != "online" {
t.Errorf("status = %q, want 'online'", meta.Status)
}
res, got := GetResources("local")
if !got {
t.Fatal("GetResources('local') returned false after RegisterLocal")
}
if res.DataDir != dir {
t.Errorf("DataDir = %q, want %q", res.DataDir, dir)
}
if res.Runtime == nil {
t.Error("local resources Runtime should not be nil")
}
ok2 := RegisterLocal(WithDataDir(dir))
if !ok2 {
t.Error("second RegisterLocal should return true (idempotent)")
}
res.Close()
}
func TestRegisterLocal_NoRegistry(t *testing.T) {
origReg := registry.Global()
defer func() {
if origReg != nil {
registry.Init(nil)
}
}()
ok := RegisterLocal()
_ = ok
}
func TestRegisterLocal_NoDocker(t *testing.T) {
registry.Init(nil)
ok := RegisterLocal(WithDataDir(t.TempDir()))
if !ok {
return
}
res, got := GetResources("local")
if got && res != nil {
res.Close()
}
}
func TestConnResourcesCloseNil(t *testing.T) {
var r *ConnResources
if err := r.Close(); err != nil {
t.Errorf("Close on nil should return nil, got %v", err)
}
}