mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 02:55:51 +00:00
240 lines
8.8 KiB
Go
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")
|
|
}
|
|
}
|