package cli import ( "bytes" "context" "log/slog" "strings" "testing" ) func TestDaemonPprofFlag(t *testing.T) { f := daemonCmd.Flags().Lookup("pprof") if f == nil { t.Fatal("--pprof flag not registered on daemonCmd") } if f.DefValue != "" { t.Errorf("--pprof default = %q, want empty", f.DefValue) } } // captureSlog swaps slog.Default() for a text handler writing to buf, // returning a buffer and a restore func. Tests use this to observe // warnDeprecated output (which uses the package-level slog.Default). func captureSlog(t *testing.T) (*bytes.Buffer, func()) { t.Helper() var buf bytes.Buffer prev := slog.Default() logger := slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelWarn})) slog.SetDefault(logger) return &buf, func() { slog.SetDefault(prev) } } // runDaemonHermetic invokes daemonCmd.RunE with a context that is // already cancelled and an unbindable --addr, so the long-running // server start short-circuits and RunE returns quickly without // touching the network. It returns whatever RunE returned and the // captured slog buffer. func runDaemonHermetic(t *testing.T, suppressWarnings bool) (string, error) { t.Helper() _, cleanup := initTestEnv(t) defer cleanup() resetRootFlags(t) buf, restore := captureSlog(t) defer restore() if suppressWarnings { _ = rootCmd.PersistentFlags().Set("no-deprecation-warnings", "true") } daemonAddr = "127.0.0.1:99999" // unbindable: port outside uint16 range → ListenAndServe fails fast ctx, cancel := context.WithCancel(context.Background()) cancel() // already-done context: the select returns via <-ctx.Done() immediately cmd := daemonCmd cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs(nil) cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) return buf.String(), err } // TestDaemonEmitsDeprecationWarning verifies REQ-068: `orca daemon` // emits a slog.Warn deprecation banner on every run. func TestDaemonEmitsDeprecationWarning(t *testing.T) { out, _ := runDaemonHermetic(t, false) if !strings.Contains(out, "orca daemon is deprecated in v0.9") { t.Errorf("expected deprecation warning in slog output, got:\n%s", out) } if !strings.Contains(out, "R-001") { t.Errorf("deprecation warning should reference R-001, got:\n%s", out) } } // TestDaemonDeprecationWarningSuppressed verifies that // --no-deprecation-warnings suppresses the deprecation banner (for // `orca upgrade` migrations). func TestDaemonDeprecationWarningSuppressed(t *testing.T) { out, _ := runDaemonHermetic(t, true) if strings.Contains(out, "deprecated in v0.9") { t.Errorf("--no-deprecation-warnings should suppress the deprecation warning, got:\n%s", out) } } // TestDaemonStillRuns verifies deprecation ≠ removal: the daemon // command's RunE is still wired and callable. We don't assert on the // error value (the hermetic short-circuit may return nil or a // shutdown-related error), only that the command did not fail *because* // of the deprecation notice. func TestDaemonStillRuns(t *testing.T) { _, err := runDaemonHermetic(t, false) if err != nil && strings.Contains(err.Error(), "deprecated") { t.Errorf("daemon must not error due to deprecation, got: %v", err) } } // TestWarnDeprecatedGate verifies the package-level helper that gates // deprecation warnings on the --no-deprecation-warnings flag. func TestWarnDeprecatedGate(t *testing.T) { t.Run("emits by default", func(t *testing.T) { buf, restore := captureSlog(t) defer restore() noDeprecationWarnings = false warnDeprecated("test-deprecation-marker") if !strings.Contains(buf.String(), "test-deprecation-marker") { t.Errorf("expected warning emitted, got: %s", buf.String()) } }) t.Run("suppressed when flag set", func(t *testing.T) { buf, restore := captureSlog(t) defer restore() noDeprecationWarnings = true defer func() { noDeprecationWarnings = false }() warnDeprecated("should-not-appear") if strings.Contains(buf.String(), "should-not-appear") { t.Errorf("expected no warning when --no-deprecation-warnings set, got: %s", buf.String()) } }) } // TestNoDeprecationWarningsFlagRegistered verifies the // --no-deprecation-warnings persistent flag exists on rootCmd. func TestNoDeprecationWarningsFlagRegistered(t *testing.T) { f := rootCmd.PersistentFlags().Lookup("no-deprecation-warnings") if f == nil { t.Fatal("--no-deprecation-warnings persistent flag not registered on rootCmd") } if f.DefValue != "false" { t.Errorf("--no-deprecation-warnings default = %q, want false", f.DefValue) } } // TestCertEmitsDeprecationWarning verifies REQ-068: `orca cert` // subcommands emit a deprecation banner. func TestCertEmitsDeprecationWarning(t *testing.T) { _, cleanup := initTestEnv(t) defer cleanup() resetRootFlags(t) buf, restore := captureSlog(t) defer restore() var out bytes.Buffer rootCmd.SetOut(&out) rootCmd.SetErr(&out) rootCmd.SetArgs([]string{"cert", "fingerprint", "--which", "ca"}) _ = rootCmd.Execute() logged := buf.String() if !strings.Contains(logged, "orca cert is deprecated in v0.9") { t.Errorf("expected cert deprecation warning, got:\n%s", logged) } if !strings.Contains(logged, "step-ca") { t.Errorf("deprecation warning should mention step-ca, got:\n%s", logged) } } // TestCertDeprecationWarningSuppressed verifies --no-deprecation-warnings // suppresses the cert deprecation banner. func TestCertDeprecationWarningSuppressed(t *testing.T) { _, cleanup := initTestEnv(t) defer cleanup() resetRootFlags(t) _ = rootCmd.PersistentFlags().Set("no-deprecation-warnings", "true") buf, restore := captureSlog(t) defer restore() var out bytes.Buffer rootCmd.SetOut(&out) rootCmd.SetErr(&out) rootCmd.SetArgs([]string{"cert", "fingerprint", "--which", "ca"}) _ = rootCmd.Execute() if strings.Contains(buf.String(), "orca cert is deprecated") { t.Errorf("--no-deprecation-warnings should suppress cert warning, got:\n%s", buf.String()) } } // TestNodeJoinMTLSEmitsDeprecationWarning verifies REQ-068: the mTLS // join path (`orca node join` without --type proxmox) warns that the // mTLS join path is deprecated. func TestNodeJoinMTLSEmitsDeprecationWarning(t *testing.T) { _, cleanup := initTestEnv(t) defer cleanup() resetRootFlags(t) buf, restore := captureSlog(t) defer restore() var out bytes.Buffer rootCmd.SetOut(&out) rootCmd.SetErr(&out) rootCmd.SetArgs([]string{"node", "join", "--name", "dep-warning", "--addr", "10.0.0.55:8443"}) if err := rootCmd.Execute(); err != nil { t.Fatalf("node join: %v", err) } logged := buf.String() if !strings.Contains(logged, "mTLS join path is deprecated") { t.Errorf("expected mTLS join deprecation warning, got:\n%s", logged) } if !strings.Contains(logged, "R-001") { t.Errorf("deprecation warning should reference R-001, got:\n%s", logged) } } // TestNodeJoinProxmoxNoMTLSDeprecationWarning verifies the deprecation // warning does NOT fire for the proxmox SSH path (that path is the // v0.9 replacement, not the deprecated mTLS path). func TestNodeJoinProxmoxNoMTLSDeprecationWarning(t *testing.T) { _, cleanup := initTestEnv(t) defer cleanup() resetRootFlags(t) buf, restore := captureSlog(t) defer restore() var out bytes.Buffer rootCmd.SetOut(&out) rootCmd.SetErr(&out) // proxmox path errors on missing --host before reaching the warning, // and never calls joinLocal, so no mTLS deprecation warning fires. rootCmd.SetArgs([]string{"node", "join", "--type", "proxmox", "--password", "x"}) _ = rootCmd.Execute() if strings.Contains(buf.String(), "mTLS join path is deprecated") { t.Errorf("proxmox path must not emit mTLS deprecation warning, got:\n%s", buf.String()) } }