mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-06 15:37:13 +00:00
Contract-Neutral: The first-session agent install handoff keeps the existing API and setup contracts while binding runtime handlers to the Router-owned canonical config through startup and monitor reloads.
140 lines
5.1 KiB
Go
140 lines
5.1 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
|
)
|
|
|
|
func decodeHostAgentInstallResponse(t *testing.T, rec *httptest.ResponseRecorder) AgentInstallCommandResponse {
|
|
t.Helper()
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("expected status OK, got %d: %s", rec.Code, rec.Body.String())
|
|
}
|
|
var resp AgentInstallCommandResponse
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func TestHandleAgentInstallCommand_HostWithCommands(t *testing.T) {
|
|
cfg := &config.Config{DataPath: t.TempDir(), AuthUser: "admin", AuthPass: "hashed-password"}
|
|
handler := newTestConfigHandlers(t, cfg)
|
|
|
|
body := []byte(`{"type":"host","enableCommands":true,"name":"nuc-agent"}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/agent-install-command", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
handler.HandleAgentInstallCommand(rec, req)
|
|
|
|
resp := decodeHostAgentInstallResponse(t, rec)
|
|
if resp.Token == "" {
|
|
t.Fatalf("expected token in response")
|
|
}
|
|
if resp.Record == nil {
|
|
t.Fatalf("expected token record in response")
|
|
}
|
|
if resp.Record.Name != "nuc-agent" {
|
|
t.Fatalf("expected requested token name, got %q", resp.Record.Name)
|
|
}
|
|
if len(cfg.APITokens) != 1 {
|
|
t.Fatalf("expected API token to be persisted")
|
|
}
|
|
record := cfg.APITokens[0]
|
|
if !record.HasScope(config.ScopeAgentExec) {
|
|
t.Fatalf("expected exec scope on commands-enabled host install token, got %v", record.Scopes)
|
|
}
|
|
if !record.HasScope(config.ScopeDockerReport) || !record.HasScope(config.ScopeAgentReport) {
|
|
t.Fatalf("expected reporting scopes on host install token, got %v", record.Scopes)
|
|
}
|
|
if got := record.Metadata["install_type"]; got != agentInstallTypeHost {
|
|
t.Fatalf("install_type metadata = %q, want %q", got, agentInstallTypeHost)
|
|
}
|
|
if got := record.Metadata["issued_via"]; got != agentInstallIssuedViaConfig {
|
|
t.Fatalf("issued_via metadata = %q, want %q", got, agentInstallIssuedViaConfig)
|
|
}
|
|
if !canBindAgentInstallExecToken(&record, "agent-nuc", "nuc") {
|
|
t.Fatalf("expected host install token to be eligible for first-use exec binding")
|
|
}
|
|
}
|
|
|
|
func TestHandleAgentInstallCommand_HostWithoutCommands(t *testing.T) {
|
|
cfg := &config.Config{DataPath: t.TempDir(), AuthUser: "admin", AuthPass: "hashed-password"}
|
|
handler := newTestConfigHandlers(t, cfg)
|
|
|
|
body := []byte(`{"type":"host","enableCommands":false}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/agent-install-command", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
handler.HandleAgentInstallCommand(rec, req)
|
|
|
|
resp := decodeHostAgentInstallResponse(t, rec)
|
|
if resp.Token == "" {
|
|
t.Fatalf("expected token in response")
|
|
}
|
|
if len(cfg.APITokens) != 1 {
|
|
t.Fatalf("expected API token to be persisted")
|
|
}
|
|
record := cfg.APITokens[0]
|
|
if record.HasScope(config.ScopeAgentExec) {
|
|
t.Fatalf("expected no exec scope without enableCommands, got %v", record.Scopes)
|
|
}
|
|
}
|
|
|
|
func TestHandleAgentInstallCommand_HostOmitsTokenWhenAuthOptional(t *testing.T) {
|
|
cfg := &config.Config{DataPath: t.TempDir()}
|
|
handler := newTestConfigHandlers(t, cfg)
|
|
|
|
body := []byte(`{"type":"host","enableCommands":true}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/agent-install-command", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
handler.HandleAgentInstallCommand(rec, req)
|
|
|
|
resp := decodeHostAgentInstallResponse(t, rec)
|
|
if resp.Token != "" {
|
|
t.Fatalf("expected optional-auth host install response to omit token, got %q", resp.Token)
|
|
}
|
|
if len(cfg.APITokens) != 0 {
|
|
t.Fatalf("expected optional-auth host install to avoid persisting API tokens")
|
|
}
|
|
}
|
|
|
|
func TestRouterSetupHandoffUsesCanonicalRuntimeConfig(t *testing.T) {
|
|
dataDir := t.TempDir()
|
|
runtimeConfig := &config.Config{DataPath: dataDir, ConfigPath: dataDir}
|
|
monitorConfig := &config.Config{DataPath: dataDir, ConfigPath: dataDir}
|
|
|
|
monitor, err := monitoring.New(monitorConfig)
|
|
if err != nil {
|
|
t.Fatalf("monitoring.New: %v", err)
|
|
}
|
|
t.Cleanup(monitor.Stop)
|
|
|
|
router := NewRouter(runtimeConfig, monitor, nil, nil, func() error { return nil }, "1.0.0")
|
|
t.Cleanup(router.shutdownBackgroundWorkers)
|
|
|
|
// The first-session security setup updates the Router-owned config after
|
|
// routes have already been wired. The agent-install handoff must observe
|
|
// that same canonical config instead of the monitor's startup snapshot.
|
|
config.Mu.Lock()
|
|
runtimeConfig.AuthUser = "admin"
|
|
runtimeConfig.AuthPass = "hashed-password"
|
|
config.Mu.Unlock()
|
|
|
|
body := []byte(`{"type":"host","enableCommands":false}`)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/agent-install-command", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
router.configHandlers.HandleAgentInstallCommand(rec, req)
|
|
|
|
resp := decodeHostAgentInstallResponse(t, rec)
|
|
if resp.Token == "" || resp.Record == nil {
|
|
t.Fatalf("expected setup handoff to create a scoped install token")
|
|
}
|
|
if got := router.configHandlers.getConfig(req.Context()); got != runtimeConfig {
|
|
t.Fatalf("config handler uses config %#v, want Router config %#v", got, runtimeConfig)
|
|
}
|
|
}
|