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

240 lines
8.8 KiB
Go

package configapi
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
func displayNameTestInstance() config.PVEInstance {
return config.PVEInstance{
Name: "cluster",
IsCluster: true,
ClusterEndpoints: []config.ClusterEndpoint{
{NodeName: "pve1", NodeIdentity: "cluster-pve1"},
{NodeName: "pve2", NodeIdentity: "cluster-pve2"},
},
ClusterNodeIdentities: []config.PVEClusterNodeIdentity{
{ID: "cluster-pve1", NativeName: "pve1"},
{ID: "cluster-pve2", NativeName: "pve2"},
},
}
}
func TestApplyClusterNodeDisplayNameOverridesSupportsUnicodeDuplicatesAndClearing(t *testing.T) {
instance := displayNameTestInstance()
updated, err := applyClusterNodeDisplayNameOverrides(instance, []ClusterNodeDisplayNameOverrideRequest{
{NodeIdentity: "cluster-pve1", DisplayName: " Rendu \u00c9tage "},
{NodeIdentity: "cluster-pve2", DisplayName: "Rendu \u00c9tage"},
})
if err != nil {
t.Fatalf("apply display names: %v", err)
}
if updated.ClusterNodeIdentities[0].DisplayName != "Rendu \u00c9tage" ||
updated.ClusterNodeIdentities[1].DisplayName != "Rendu \u00c9tage" {
t.Fatalf("cosmetic duplicate Unicode labels should be allowed: %+v", updated.ClusterNodeIdentities)
}
if instance.ClusterNodeIdentities[0].DisplayName != "" {
t.Fatal("display-name update mutated the caller's config slice")
}
cleared, err := applyClusterNodeDisplayNameOverrides(updated, []ClusterNodeDisplayNameOverrideRequest{{
NodeIdentity: "cluster-pve1",
DisplayName: "",
}})
if err != nil || cleared.ClusterNodeIdentities[0].DisplayName != "" {
t.Fatalf("clear override failed: identities=%+v err=%v", cleared.ClusterNodeIdentities, err)
}
}
func TestApplyClusterNodeDisplayNameOverridesRejectsStaleAndAmbiguousRequests(t *testing.T) {
instance := displayNameTestInstance()
cases := []struct {
name string
overrides []ClusterNodeDisplayNameOverrideRequest
errorText string
}{
{
name: "unknown identity",
overrides: []ClusterNodeDisplayNameOverrideRequest{{NodeIdentity: "missing", DisplayName: "Name"}},
errorText: "unknown cluster node identity",
},
{
name: "duplicate request",
overrides: []ClusterNodeDisplayNameOverrideRequest{
{NodeIdentity: "cluster-pve1", DisplayName: "A"},
{NodeIdentity: "cluster-pve1", DisplayName: "B"},
},
errorText: "provided more than once",
},
{
name: "control character",
overrides: []ClusterNodeDisplayNameOverrideRequest{{NodeIdentity: "cluster-pve1", DisplayName: "bad\nname"}},
errorText: "control characters",
},
{
name: "too long",
overrides: []ClusterNodeDisplayNameOverrideRequest{{
NodeIdentity: "cluster-pve1",
DisplayName: strings.Repeat("\u754c", 129),
}},
errorText: "at most 128",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := applyClusterNodeDisplayNameOverrides(instance, tc.overrides)
if err == nil || !strings.Contains(err.Error(), tc.errorText) {
t.Fatalf("error = %v, want containing %q", err, tc.errorText)
}
})
}
}
func TestClusterEndpointResponseSeparatesNativeAndDisplayIdentity(t *testing.T) {
instance := displayNameTestInstance()
instance.ClusterNodeIdentities[0].NativeNodeID = 11
instance.ClusterNodeIdentities[0].NativeAliases = []string{"pve-old"}
instance.ClusterNodeIdentities[0].DisplayName = "Compute A"
instance.ClusterEndpoints[0].NativeNodeID = 11
response := toClusterEndpointResponse(instance.ClusterEndpoints[0], instance.ClusterNodeIdentities)
if response.NodeIdentity != "cluster-pve1" || response.NativeNodeID != 11 ||
response.NativeName != "pve1" || response.DisplayName != "Compute A" {
t.Fatalf("unexpected endpoint response: %+v", response)
}
if len(response.NativeAliases) != 1 || response.NativeAliases[0] != "pve-old" {
t.Fatalf("native diagnostic aliases missing from endpoint response: %+v", response)
}
}
func TestHandleUpdateNodePersistsDisplayNameWithoutChangingConnectionAuthority(t *testing.T) {
cfg := &config.Config{
DataPath: t.TempDir(),
PVEInstances: []config.PVEInstance{{
Name: "cluster", Host: "https://configured.example.test:8006",
TokenName: "root@pam!pulse", TokenValue: "secret",
IsCluster: true, ClusterName: "production",
ClusterEndpoints: []config.ClusterEndpoint{{
NodeID: "node/pve1", NodeIdentity: "production-pve1", NativeNodeID: 1,
NodeName: "pve1", Host: "https://pve1:8006", IP: "10.0.0.1", IPOverride: "10.20.0.1",
}},
ClusterNodeIdentities: []config.PVEClusterNodeIdentity{{
ID: "production-pve1", NativeNodeID: 1, NativeName: "pve1",
}},
}},
}
handler := newTestConfigHandlers(t, cfg)
body, _ := json.Marshal(map[string]any{
"clusterNodeDisplayNameOverrides": []map[string]string{{
"nodeIdentity": "production-pve1",
"displayName": "Render East",
}},
})
req := httptest.NewRequest(http.MethodPut, "/api/config/nodes/pve-0", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleUpdateNode(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("update status = %d: %s", rec.Code, rec.Body.String())
}
instance := cfg.PVEInstances[0]
if instance.ClusterNodeIdentities[0].DisplayName != "Render East" {
t.Fatalf("display name was not persisted in config: %+v", instance.ClusterNodeIdentities)
}
if instance.Host != "https://configured.example.test:8006" ||
instance.TokenValue != "secret" ||
instance.ClusterEndpoints[0].Host != "https://pve1:8006" ||
instance.ClusterEndpoints[0].IPOverride != "10.20.0.1" {
t.Fatalf("presentation update changed routing or credentials: %+v", instance)
}
reloaded, err := handler.defaultPersistence.LoadNodesConfig()
if err != nil {
t.Fatalf("reload persisted nodes: %v", err)
}
if got := reloaded.PVEInstances[0].ClusterNodeIdentities[0].DisplayName; got != "Render East" {
t.Fatalf("reloaded display name = %q, want Render East", got)
}
}
func TestHandleUpdateNodeRejectDoesNotMutateLegacyIdentityState(t *testing.T) {
cfg := &config.Config{
DataPath: t.TempDir(),
PVEInstances: []config.PVEInstance{{
Name: "cluster", Host: "https://configured.example.test:8006",
TokenName: "root@pam!pulse", TokenValue: "secret",
IsCluster: true, ClusterName: "production",
ClusterEndpoints: []config.ClusterEndpoint{{NodeName: "pve1"}},
}},
}
handler := newTestConfigHandlers(t, cfg)
body, _ := json.Marshal(map[string]any{
"clusterNodeDisplayNameOverrides": []map[string]string{{
"nodeIdentity": "stale-browser-identity",
"displayName": "Wrong node",
}},
})
req := httptest.NewRequest(http.MethodPut, "/api/config/nodes/pve-0", bytes.NewReader(body))
rec := httptest.NewRecorder()
handler.HandleUpdateNode(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("update status = %d, want 400: %s", rec.Code, rec.Body.String())
}
if cfg.PVEInstances[0].ClusterEndpoints[0].NodeIdentity != "" ||
len(cfg.PVEInstances[0].ClusterNodeIdentities) != 0 {
t.Fatalf("rejected update mutated live legacy config: %+v", cfg.PVEInstances[0])
}
}
func TestMergeDiscoveredClusterEndpointConfigurationUsesStrongIdentityOnly(t *testing.T) {
existing := []config.ClusterEndpoint{
{NodeIdentity: "identity-a", NativeNodeID: 1, NodeName: "old-a", IP: "10.0.0.1", IPOverride: "route-a"},
{NodeIdentity: "identity-b", NativeNodeID: 2, NodeName: "old-b", IP: "10.0.0.2", IPOverride: "route-b"},
}
renamed := mergeDiscoveredClusterEndpointConfiguration(existing, []config.ClusterEndpoint{{
NativeNodeID: 1, NodeName: "new-a", IP: "10.20.0.1",
}})
if renamed[0].NodeIdentity != "identity-a" || renamed[0].IPOverride != "route-a" {
t.Fatalf("numeric identity did not preserve user metadata through rename/re-IP: %+v", renamed[0])
}
ambiguousLegacy := mergeDiscoveredClusterEndpointConfiguration([]config.ClusterEndpoint{
{NodeIdentity: "identity-a", NodeName: "old-a", IP: "10.0.0.1"},
{NodeIdentity: "identity-b", NodeName: "old-b", IP: "10.0.0.1"},
}, []config.ClusterEndpoint{{
NodeName: "new-name", IP: "10.0.0.1",
}})
if ambiguousLegacy[0].NodeIdentity != "" {
t.Fatalf("ambiguous legacy address evidence must not corrupt canonical identity: %+v", ambiguousLegacy[0])
}
}
func TestApplyClusterNodeDisplayNameOverridesConcurrentCopies(t *testing.T) {
instance := displayNameTestInstance()
var wait sync.WaitGroup
for worker := 0; worker < 32; worker++ {
wait.Add(1)
go func() {
defer wait.Done()
updated, err := applyClusterNodeDisplayNameOverrides(instance, []ClusterNodeDisplayNameOverrideRequest{{
NodeIdentity: "cluster-pve1",
DisplayName: "Render East",
}})
if err != nil || updated.ClusterNodeIdentities[0].DisplayName != "Render East" {
t.Errorf("concurrent immutable update failed: identities=%+v err=%v", updated.ClusterNodeIdentities, err)
}
}()
}
wait.Wait()
if instance.ClusterNodeIdentities[0].DisplayName != "" {
t.Fatal("concurrent presentation updates mutated shared tenant config input")
}
}