Files
pulse/internal/api/issue1644_exec_binding_integration_test.go
T
2026-08-21 14:56:07 +01:00

86 lines
3.6 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
// Issue #1644 follow-up: the auto-register path writes bound_hostname for host
// install tokens without bound_agent_id or a binding version. The first
// command-channel enrollment must take the clean first-use bind.
func TestIssue1644AutoRegisteredHostTokenBindsExecOnFirstUse(t *testing.T) {
stubAutoRegisterNetworkDeps(t)
dataPath := t.TempDir()
cfg := &config.Config{DataPath: dataPath, AuthUser: "admin", AuthPass: "hashed-password"}
handler := newTestConfigHandlers(t, cfg)
installReq := httptest.NewRequest(http.MethodPost, "/api/agent-install-command", strings.NewReader(`{"type":"host","name":"issue-1644-exec","enableCommands":true}`))
installReq.Host = "pulse.example:7655"
installRec := httptest.NewRecorder()
handler.HandleAgentInstallCommand(installRec, installReq)
if installRec.Code != http.StatusOK {
t.Fatalf("host install token mint status = %d, body=%s", installRec.Code, installRec.Body.String())
}
var install AgentInstallCommandResponse
if err := json.Unmarshal(installRec.Body.Bytes(), &install); err != nil {
t.Fatalf("decode host install token response: %v", err)
}
rawToken := strings.TrimSpace(install.Token)
if rawToken == "" {
t.Fatal("host install token mint omitted runtime token")
}
if !cfg.APITokens[0].HasScope(config.ScopeAgentExec) {
t.Fatalf("commands-enabled host install token is missing %s: %v", config.ScopeAgentExec, cfg.APITokens[0].Scopes)
}
registerRec := runAgentAutoRegister(t, handler, rawToken, AutoRegisterRequest{
Type: "pve", Host: "https://pve-exec.local:8006", TokenID: "pulse-monitor@pve!pulse-pve-exec",
TokenValue: "proxmox-secret", ServerName: "pve-exec", Source: "agent",
})
if registerRec.Code != http.StatusOK {
t.Fatalf("host-token pve registration status = %d, body=%s", registerRec.Code, registerRec.Body.String())
}
if got := cfg.APITokens[0].Metadata["bound_hostname"]; got != "pve-exec" {
t.Fatalf("auto-register bound hostname = %q, want %q", got, "pve-exec")
}
if got := strings.TrimSpace(cfg.APITokens[0].Metadata["bound_agent_id"]); got != "" {
t.Fatalf("auto-register wrote bound_agent_id = %q; the exec first-use path assumes it is empty", got)
}
decision := evaluateAgentExecBinding(&cfg.APITokens[0], "agent-machine-id", "pve-exec.lan")
if !decision.admit || !decision.firstBind {
t.Fatalf("auto-registered install token exec decision = %+v, want a clean first bind", decision)
}
if decision.legacyMigrate {
t.Fatal("auto-registered install token was admitted through the legacy migration branch")
}
router := &Router{config: cfg, persistence: config.NewConfigPersistence(dataPath)}
admission, ok := router.admitAgentExecToken(rawToken, "agent-machine-id", "pve-exec.lan")
if !ok {
t.Fatal("auto-registered host install token was rejected on the command channel")
}
if admission.AgentID != "agent-machine-id" {
t.Fatalf("admitted agent id = %q, want %q", admission.AgentID, "agent-machine-id")
}
config.Mu.RLock()
defer config.Mu.RUnlock()
bound := cfg.APITokens[0].Metadata
if got := bound["bound_agent_id"]; got != "agent-machine-id" {
t.Fatalf("bound_agent_id = %q, want %q", got, "agent-machine-id")
}
if got := bound[agentExecBindingVersionKey]; got != agentExecBindingVersion {
t.Fatalf("%s = %q, want %q", agentExecBindingVersionKey, got, agentExecBindingVersion)
}
if got := bound["bound_hostname"]; got != "pve-exec" {
t.Fatalf("bound_hostname after exec bind = %q, want the auto-registered %q", got, "pve-exec")
}
}