A self-hosted orchestration system crafted for power users. composia.xyz
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163//go:build e2e
package e2e
import ( "context" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "time")
const ( defaultControllerAddr = "http://127.0.0.1:7001" defaultAccessToken = "dev-admin-token")
type cliResult struct { stdout string stderr string err error}
func TestCLIConnectsToRealController(t *testing.T) { bin := buildCLI(t) env := cliEnv(t)
waitForController(t, bin, env)
status := runCLI(t, bin, env, "--output", "json", "system", "status") if status.err != nil { t.Fatalf("system status failed: %v\nstdout:\n%s\nstderr:\n%s", status.err, status.stdout, status.stderr) } statusJSON := decodeJSON(t, status.stdout) if version, ok := statusJSON["version"].(string); !ok || version == "" { t.Fatalf("expected system status version, got:\n%s", status.stdout) }
nodes := runCLI(t, bin, env, "--output", "json", "node", "list") if nodes.err != nil { t.Fatalf("node list failed: %v\nstdout:\n%s\nstderr:\n%s", nodes.err, nodes.stdout, nodes.stderr) } nodesJSON := decodeJSON(t, nodes.stdout) if !hasNodeID(nodesJSON, "main") { t.Fatalf("expected node list to include main, got:\n%s", nodes.stdout) }}
func buildCLI(t *testing.T) string { t.Helper()
bin := filepath.Join(t.TempDir(), "composia") if runtime.GOOS == "windows" { bin += ".exe" }
cmd := exec.Command("go", "build", "-o", bin, "./cmd/composia") cmd.Dir = repoRoot(t) out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("build CLI: %v\n%s", err, out) }
return bin}
func repoRoot(t *testing.T) string { t.Helper()
root, err := filepath.Abs(filepath.Join("..", "..")) if err != nil { t.Fatalf("resolve repo root: %v", err) } return root}
func cliEnv(t *testing.T) []string { t.Helper()
addr := strings.TrimSpace(os.Getenv("COMPOSIA_E2E_CONTROLLER_ADDR")) if addr == "" { addr = defaultControllerAddr } token := strings.TrimSpace(os.Getenv("COMPOSIA_E2E_ACCESS_TOKEN")) if token == "" { token = defaultAccessToken }
home := t.TempDir() env := append(os.Environ(), "COMPOSIA_CONTROLLER_ADDR="+addr, "COMPOSIA_ACCESS_TOKEN="+token, "HOME="+home, "XDG_CONFIG_HOME="+filepath.Join(home, ".config"), ) return env}
func waitForController(t *testing.T, bin string, env []string) { t.Helper()
deadline := time.Now().Add(60 * time.Second) var last cliResult for time.Now().Before(deadline) { last = runCLI(t, bin, env, "--output", "json", "system", "status") if last.err == nil { return } time.Sleep(2 * time.Second) }
t.Fatalf("controller did not become ready: %v\nstdout:\n%s\nstderr:\n%s", last.err, last.stdout, last.stderr)}
func runCLI(t *testing.T, bin string, env []string, args ...string) cliResult { t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel()
cmd := exec.CommandContext(ctx, bin, args...) cmd.Env = env
var stdout, stderr strings.Builder cmd.Stdout = &stdout cmd.Stderr = &stderr
err := cmd.Run() if ctx.Err() != nil { err = fmt.Errorf("CLI timed out: %w", ctx.Err()) } return cliResult{stdout: stdout.String(), stderr: stderr.String(), err: err}}
func decodeJSON(t *testing.T, input string) map[string]any { t.Helper()
var decoded map[string]any if err := json.Unmarshal([]byte(input), &decoded); err != nil { t.Fatalf("decode JSON: %v\n%s", err, input) } return decoded}
func hasNodeID(input map[string]any, nodeID string) bool { nodes, ok := input["nodes"].([]any) if !ok { return false } for _, item := range nodes { node, ok := item.(map[string]any) if ok && node["node_id"] == nodeID { return true } } return false}