package cli import ( "context" "io" "os" "path/filepath" "testing" "time" "git.cloudinit.dev/coreci/orca/internal/certpaths" "git.cloudinit.dev/coreci/orca/internal/model" "git.cloudinit.dev/coreci/orca/internal/store" ) // initTestEnv sets ORCA_HOME to a temp dir and returns a cleanup func. func initTestEnv(t *testing.T) (string, func()) { t.Helper() dir := t.TempDir() orig := os.Getenv("ORCA_HOME") if err := os.Setenv("ORCA_HOME", dir); err != nil { t.Fatalf("set ORCA_HOME: %v", err) } return dir, func() { if err := os.Setenv("ORCA_HOME", orig); err != nil { t.Fatalf("restore ORCA_HOME: %v", err) } } } // discardWriter is an io.Writer that discards all output (for tests // that don't need to inspect init stdout). type discardWriter struct{} func (discardWriter) Write(p []byte) (int, error) { return len(p), nil } var _ io.Writer = discardWriter{} func TestInit_FullBootstrap(t *testing.T) { dir, cleanup := initTestEnv(t) defer cleanup() if err := runInit(discardWriter{}); err != nil { t.Fatalf("init: %v", err) } // Verify namespace dir exists. if _, err := os.Stat(dir); err != nil { t.Errorf("namespace dir missing: %v", err) } // Verify CA files exist with correct modes. caCert := certpaths.CACertPath() caKey := certpaths.CAKeyPath() if _, err := os.Stat(caCert); err != nil { t.Errorf("ca.crt missing: %v", err) } if info, err := os.Stat(caKey); err == nil { if info.Mode().Perm() != 0o600 { t.Errorf("ca.key mode = %04o, want 0600", info.Mode().Perm()) } } else { t.Errorf("ca.key missing: %v", err) } // Verify server cert exists. if _, err := os.Stat(certpaths.ServerCertPath()); err != nil { t.Errorf("server.crt missing: %v", err) } // Verify DB exists and has migrations applied. db, err := store.Open(certpaths.DBPath()) if err != nil { t.Fatalf("open db: %v", err) } defer db.Close() ctx := context.Background() version, err := store.MigrationVersion(ctx, db) if err != nil { t.Fatalf("migration version: %v", err) } if version != "0006_node_kind_os.sql" { t.Errorf("migration version = %q, want 0006_node_kind_os.sql", version) } // Verify localhost node registered with kind=localhost. repo := store.NewNodeRepo(db) node, err := repo.GetByName(ctx, "localhost") if err != nil { t.Fatalf("get localhost node: %v", err) } if node.Kind != string(model.NodeKindLocalhost) { t.Errorf("node kind = %q, want localhost", node.Kind) } if node.OS == "" { t.Errorf("node os is empty, expected detected value") } if node.Address != "localhost:8443" { t.Errorf("node address = %q, want localhost:8443", node.Address) } } func TestInit_IdempotentReRun(t *testing.T) { _, cleanup := initTestEnv(t) defer cleanup() // First init. if err := runInit(discardWriter{}); err != nil { t.Fatalf("first init: %v", err) } // Capture first-run state. caCertBefore, _ := os.ReadFile(certpaths.CACertPath()) serverCertBefore, _ := os.ReadFile(certpaths.ServerCertPath()) db, err := store.Open(certpaths.DBPath()) if err != nil { t.Fatalf("open db: %v", err) } repo := store.NewNodeRepo(db) ctx := context.Background() nodeBefore, err := repo.GetByName(ctx, "localhost") if err != nil { t.Fatalf("get node before: %v", err) } nodeIDBefore := nodeBefore.ID joinedAtBefore := nodeBefore.JoinedAt if err := db.Close(); err != nil { t.Fatalf("close db: %v", err) } // Wait a moment so last_seen can differ. time.Sleep(50 * time.Millisecond) // Second init (should be idempotent). if err := runInit(discardWriter{}); err != nil { t.Fatalf("second init: %v", err) } // CA and server cert must NOT have been regenerated. caCertAfter, _ := os.ReadFile(certpaths.CACertPath()) serverCertAfter, _ := os.ReadFile(certpaths.ServerCertPath()) if string(caCertBefore) != string(caCertAfter) { t.Error("CA was regenerated on re-run (D-036 violation)") } if string(serverCertBefore) != string(serverCertAfter) { t.Error("server cert was regenerated on re-run (D-036 violation)") } // Node ID and joined_at must be unchanged; last_seen should be refreshed. db, err = store.Open(certpaths.DBPath()) if err != nil { t.Fatalf("reopen db: %v", err) } defer db.Close() repo = store.NewNodeRepo(db) nodeAfter, err := repo.GetByName(ctx, "localhost") if err != nil { t.Fatalf("get node after: %v", err) } if nodeAfter.ID != nodeIDBefore { t.Errorf("node id changed: was %s, now %s (D-036 violation)", nodeIDBefore, nodeAfter.ID) } if !nodeAfter.JoinedAt.Equal(joinedAtBefore) { t.Errorf("joined_at changed: was %v, now %v (D-036 violation)", joinedAtBefore, nodeAfter.JoinedAt) } if !nodeAfter.LastSeen.After(joinedAtBefore) { t.Errorf("last_seen not refreshed: was %v, now %v", joinedAtBefore, nodeAfter.LastSeen) } // No duplicate localhost nodes. nodes, err := repo.List(ctx) if err != nil { t.Fatalf("list nodes: %v", err) } localhostCount := 0 for _, n := range nodes { if n.Name == "localhost" { localhostCount++ } } if localhostCount != 1 { t.Errorf("found %d localhost nodes, want 1 (idempotency)", localhostCount) } } func TestInit_NamespaceDirCreation(t *testing.T) { dir, cleanup := initTestEnv(t) defer cleanup() // The namespace dir is the ORCA_HOME temp dir itself — but let's // point at a non-existent subdir to test MkdirAll. subDir := filepath.Join(dir, "nested", "orca-state") if err := os.Setenv("ORCA_HOME", subDir); err != nil { t.Fatalf("set ORCA_HOME: %v", err) } if err := runInit(discardWriter{}); err != nil { t.Fatalf("init with nested dir: %v", err) } if _, err := os.Stat(subDir); err != nil { t.Errorf("nested namespace dir not created: %v", err) } }