Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions internal/cli/daemon_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"io"
"net"
neturl "net/url"
"os"
Expand All @@ -25,6 +26,9 @@ var (
installHTTPDaemon = urlscheme.InstallHTTPDaemon
uninstallHTTPDaemon = urlscheme.UninstallHTTPDaemon
getHTTPDaemonStatus = urlscheme.GetHTTPDaemonStatus

daemonInstallJSONWriter io.Writer = os.Stdout
daemonInstallLogWriter io.Writer = os.Stderr
)

type daemonServeCommandOptions struct {
Expand Down Expand Up @@ -287,13 +291,26 @@ func defaultDaemonInstallCommandRunner(ctx context.Context, options daemonInstal
ListenAddress: options.ListenAddress,
})
if err != nil {
_, _ = fmt.Fprintf(daemonInstallLogWriter, "daemon install failed: %v\n", err)
_, _ = fmt.Fprintf(daemonInstallLogWriter, "remedy: run `%s daemon install --listen %s`\n", executablePath, options.ListenAddress)
return err
}
return encodeJSONLine(os.Stdout, map[string]any{
"status": "ok",
"listen_address": result.ListenAddress,
"autostart_mode": result.AutostartMode,
"hosts_warning": strings.TrimSpace(result.HostsWarning),
_, _ = fmt.Fprintf(daemonInstallLogWriter, "daemon install succeeded: autostart=%s listen=%s\n", result.AutostartMode, result.ListenAddress)
if strings.TrimSpace(result.HostsWarning) != "" {
_, _ = fmt.Fprintf(daemonInstallLogWriter, "hosts warning: %s\n", strings.TrimSpace(result.HostsWarning))
}
if result.DaemonStarted {
_, _ = fmt.Fprintf(daemonInstallLogWriter, "daemon started in background and is ready on %s\n", result.ListenAddress)
} else if strings.TrimSpace(result.DaemonStartWarning) != "" {
_, _ = fmt.Fprintf(daemonInstallLogWriter, "daemon startup warning: %s\n", strings.TrimSpace(result.DaemonStartWarning))
}
return encodeJSONLine(daemonInstallJSONWriter, map[string]any{
"status": "ok",
"listen_address": result.ListenAddress,
"autostart_mode": result.AutostartMode,
"hosts_warning": strings.TrimSpace(result.HostsWarning),
"daemon_started": result.DaemonStarted,
"daemon_start_warning": strings.TrimSpace(result.DaemonStartWarning),
})
}

Expand Down
69 changes: 65 additions & 4 deletions internal/cli/daemon_commands_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package cli

import (
"bytes"
"context"
"encoding/json"
"errors"
"net/url"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -42,25 +43,34 @@ func TestDaemonInstallDefaultRunnerUsesCurrentExecutable(t *testing.T) {
originalRunner := runDaemonInstallCommand
originalResolveExecutablePath := resolveExecutablePath
originalInstall := installHTTPDaemon
originalJSONWriter := daemonInstallJSONWriter
originalLogWriter := daemonInstallLogWriter
t.Cleanup(func() { runDaemonInstallCommand = originalRunner })
t.Cleanup(func() { resolveExecutablePath = originalResolveExecutablePath })
t.Cleanup(func() { installHTTPDaemon = originalInstall })
t.Cleanup(func() { daemonInstallJSONWriter = originalJSONWriter })
t.Cleanup(func() { daemonInstallLogWriter = originalLogWriter })

runDaemonInstallCommand = defaultDaemonInstallCommandRunner
resolveExecutablePath = func() (string, error) {
return "/tmp/neocode", nil
}
var stdout bytes.Buffer
var stderr bytes.Buffer
daemonInstallJSONWriter = &stdout
daemonInstallLogWriter = &stderr
var captured urlscheme.HTTPDaemonInstallOptions
installHTTPDaemon = func(options urlscheme.HTTPDaemonInstallOptions) (urlscheme.HTTPDaemonInstallResult, error) {
captured = options
return urlscheme.HTTPDaemonInstallResult{
ListenAddress: options.ListenAddress,
AutostartMode: "test-mode",
ListenAddress: options.ListenAddress,
AutostartMode: "test-mode",
DaemonStarted: true,
DaemonStartWarning: "",
}, nil
}

command := NewRootCommand()
command.SetOut(os.Stdout)
command.SetArgs([]string{"daemon", "install", "--listen", "127.0.0.1:19921"})
if err := command.ExecuteContext(context.Background()); err != nil {
t.Fatalf("ExecuteContext() error = %v", err)
Expand All @@ -71,6 +81,57 @@ func TestDaemonInstallDefaultRunnerUsesCurrentExecutable(t *testing.T) {
if captured.ListenAddress != "127.0.0.1:19921" {
t.Fatalf("listen address = %q, want %q", captured.ListenAddress, "127.0.0.1:19921")
}
var payload map[string]any
if err := json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), &payload); err != nil {
t.Fatalf("decode stdout json: %v", err)
}
if payload["status"] != "ok" {
t.Fatalf("status = %v, want ok", payload["status"])
}
if payload["daemon_started"] != true {
t.Fatalf("daemon_started = %v, want true", payload["daemon_started"])
}
if !strings.Contains(stderr.String(), "daemon install succeeded") {
t.Fatalf("stderr = %q, want success summary", stderr.String())
}
}

func TestDaemonInstallDefaultRunnerFailureWritesRemedy(t *testing.T) {
originalRunner := runDaemonInstallCommand
originalResolveExecutablePath := resolveExecutablePath
originalInstall := installHTTPDaemon
originalJSONWriter := daemonInstallJSONWriter
originalLogWriter := daemonInstallLogWriter
t.Cleanup(func() { runDaemonInstallCommand = originalRunner })
t.Cleanup(func() { resolveExecutablePath = originalResolveExecutablePath })
t.Cleanup(func() { installHTTPDaemon = originalInstall })
t.Cleanup(func() { daemonInstallJSONWriter = originalJSONWriter })
t.Cleanup(func() { daemonInstallLogWriter = originalLogWriter })

runDaemonInstallCommand = defaultDaemonInstallCommandRunner
resolveExecutablePath = func() (string, error) {
return "/tmp/neocode", nil
}
var stdout bytes.Buffer
var stderr bytes.Buffer
daemonInstallJSONWriter = &stdout
daemonInstallLogWriter = &stderr
installHTTPDaemon = func(options urlscheme.HTTPDaemonInstallOptions) (urlscheme.HTTPDaemonInstallResult, error) {
return urlscheme.HTTPDaemonInstallResult{}, errors.New("boom")
}

command := NewRootCommand()
command.SetArgs([]string{"daemon", "install", "--listen", "127.0.0.1:19921"})
err := command.ExecuteContext(context.Background())
if err == nil {
t.Fatal("expected install failure")
}
if !strings.Contains(stderr.String(), "remedy: run `/tmp/neocode daemon install --listen 127.0.0.1:19921`") {
t.Fatalf("stderr = %q, want remedy command", stderr.String())
}
if stdout.Len() != 0 {
t.Fatalf("stdout should be empty on failure, got %q", stdout.String())
}
}

func TestDaemonServeDoesNotExposeTokenFileFlag(t *testing.T) {
Expand Down
Loading
Loading