Pulse/internal/unifiedresources/policy_metadata_test.go
2026-03-19 17:18:18 +00:00

515 lines
16 KiB
Go

package unifiedresources
import (
"strings"
"testing"
)
func TestRefreshPolicyMetadata_ClassifiesRestrictedResources(t *testing.T) {
resource := Resource{
ID: "vm-100",
Name: "customer-payments",
Type: ResourceTypeVM,
Status: StatusOnline,
Tags: []string{"customer-data"},
Identity: ResourceIdentity{
Hostnames: []string{"payments.internal"},
IPAddresses: []string{"10.0.0.44"},
},
Canonical: &CanonicalIdentity{
PlatformID: "payments.internal",
PrimaryID: "vm:100",
Aliases: []string{"vm-100"},
},
}
RefreshPolicyMetadata(&resource)
if resource.Policy == nil {
t.Fatal("expected policy metadata")
}
if got := resource.Policy.Sensitivity; got != ResourceSensitivityRestricted {
t.Fatalf("sensitivity = %q, want %q", got, ResourceSensitivityRestricted)
}
if got := resource.Policy.Routing.Scope; got != ResourceRoutingScopeLocalOnly {
t.Fatalf("routing scope = %q, want %q", got, ResourceRoutingScopeLocalOnly)
}
if !containsRedactionHint(resource.Policy.Routing.Redact, ResourceRedactionHostname) {
t.Fatalf("expected hostname redaction hint, got %#v", resource.Policy.Routing.Redact)
}
if !containsRedactionHint(resource.Policy.Routing.Redact, ResourceRedactionIPAddress) {
t.Fatalf("expected IP redaction hint, got %#v", resource.Policy.Routing.Redact)
}
if resource.AISafeSummary == "" {
t.Fatal("expected aiSafeSummary")
}
if resource.AISafeSummary == resource.Name {
t.Fatalf("aiSafeSummary leaked raw name: %q", resource.AISafeSummary)
}
}
func TestRefreshPolicyMetadata_ClassifiesInfrastructureAsInternal(t *testing.T) {
resource := Resource{
ID: "agent-1",
Name: "pve-node",
Type: ResourceTypeAgent,
Status: StatusOnline,
Agent: &AgentData{
Hostname: "pve-node",
},
}
RefreshCanonicalIdentity(&resource)
RefreshPolicyMetadata(&resource)
if resource.Policy == nil {
t.Fatal("expected policy metadata")
}
if got := resource.Policy.Sensitivity; got != ResourceSensitivityInternal {
t.Fatalf("sensitivity = %q, want %q", got, ResourceSensitivityInternal)
}
if got := resource.Policy.Routing.Scope; got != ResourceRoutingScopeCloudSummary {
t.Fatalf("routing scope = %q, want %q", got, ResourceRoutingScopeCloudSummary)
}
}
func TestRefreshCanonicalMetadata(t *testing.T) {
resource := Resource{
ID: "agent-1",
Name: "pve-node",
Type: ResourceTypeAgent,
Status: StatusOnline,
Agent: &AgentData{
Hostname: "pve-node",
},
}
RefreshCanonicalMetadata(&resource)
if resource.Canonical == nil {
t.Fatal("expected canonical identity metadata")
}
if resource.Policy == nil {
t.Fatal("expected policy metadata")
}
if resource.AISafeSummary == "" {
t.Fatal("expected aiSafeSummary")
}
}
func TestRefreshCanonicalMetadataSlice(t *testing.T) {
input := []Resource{
{
ID: "agent-1",
Name: "pve-node",
Type: ResourceTypeAgent,
Status: StatusOnline,
Agent: &AgentData{
Hostname: "pve-node",
},
},
}
out := RefreshCanonicalMetadataSlice(input)
if input[0].Canonical != nil {
t.Fatal("expected input slice to remain unmodified")
}
if len(out) != 1 {
t.Fatalf("expected one resource, got %d", len(out))
}
if out[0].Canonical == nil {
t.Fatal("expected canonical identity metadata in output slice")
}
if out[0].Policy == nil {
t.Fatal("expected policy metadata in output slice")
}
if out[0].AISafeSummary == "" {
t.Fatal("expected aiSafeSummary in output slice")
}
}
func TestResourcePolicyPresentationLabels(t *testing.T) {
if got := ResourceSensitivityLabel(ResourceSensitivityPublic); got != "Public" {
t.Fatalf("sensitivity label = %q, want %q", got, "Public")
}
if got := ResourceRoutingScopeLabel(ResourceRoutingScopeLocalOnly); got != "Local Only" {
t.Fatalf("routing label = %q, want %q", got, "Local Only")
}
if got := ResourceRedactionHintLabel(ResourceRedactionIPAddress); got != "IP Address" {
t.Fatalf("redaction label = %q, want %q", got, "IP Address")
}
policy := &ResourcePolicy{
Routing: ResourceRoutingPolicy{
Redact: []ResourceRedactionHint{
ResourceRedactionPath,
ResourceRedactionHostname,
},
},
}
got := ResourcePolicyRedactionLabels(policy)
if len(got) != 2 || got[0] != "Hostname" || got[1] != "Path" {
t.Fatalf("redaction labels = %#v, want [Hostname Path]", got)
}
}
func TestResourceClusterName(t *testing.T) {
public := Resource{
Identity: ResourceIdentity{
ClusterName: " cluster-a ",
},
}
if got := ResourceClusterName(public); got != "cluster-a" {
t.Fatalf("ResourceClusterName() = %q, want cluster-a", got)
}
governed := Resource{
Policy: &ResourcePolicy{
Routing: ResourceRoutingPolicy{
Redact: []ResourceRedactionHint{ResourceRedactionHostname},
},
},
Kubernetes: &K8sData{
ClusterName: "k8s-prod",
},
}
if got := ResourceClusterName(governed); got != ResourcePolicyRedactedLabel {
t.Fatalf("ResourceClusterName() with governed policy = %q, want redacted label", got)
}
}
func TestResourceIPSummary(t *testing.T) {
public := Resource{
Identity: ResourceIdentity{
IPAddresses: []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"},
},
}
if got := ResourceIPSummary(public, 2); got != " - IPs 10.0.0.1, 10.0.0.2" {
t.Fatalf("ResourceIPSummary() = %q, want truncated IP list", got)
}
governed := Resource{
Policy: &ResourcePolicy{
Routing: ResourceRoutingPolicy{
Redact: []ResourceRedactionHint{ResourceRedactionIPAddress},
},
},
Identity: ResourceIdentity{
IPAddresses: []string{"10.0.0.10", "10.0.0.11"},
},
}
if got := ResourceIPSummary(governed, 10); got != " - IPs redacted by policy" {
t.Fatalf("ResourceIPSummary() with governed policy = %q, want redacted summary", got)
}
}
func TestResourcePolicyRedactionLabelsUseCanonicalOrder(t *testing.T) {
policy := &ResourcePolicy{
Routing: ResourceRoutingPolicy{
Redact: []ResourceRedactionHint{
ResourceRedactionAlias,
ResourceRedactionPath,
ResourceRedactionHostname,
ResourceRedactionPlatformID,
},
},
}
got := ResourcePolicyRedactionLabels(policy)
want := []string{"Hostname", "Platform ID", "Alias", "Path"}
if len(got) != len(want) {
t.Fatalf("redaction labels = %#v, want %#v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("redaction labels = %#v, want %#v", got, want)
}
}
}
func TestResourcePolicyCountSummariesUseCanonicalOrder(t *testing.T) {
sensitivityCounts := map[ResourceSensitivity]int{
ResourceSensitivityRestricted: 1,
ResourceSensitivityPublic: 2,
ResourceSensitivitySensitive: 3,
ResourceSensitivityInternal: 4,
}
if got := ResourcePolicySensitivitySummaryFromCounts(sensitivityCounts); len(got) != 4 || got[0] != "2 Public" || got[1] != "4 Internal" || got[2] != "3 Sensitive" || got[3] != "1 Restricted" {
t.Fatalf("sensitivity summary = %#v, want [2 Public 4 Internal 3 Sensitive 1 Restricted]", got)
}
routingCounts := map[ResourceRoutingScope]int{
ResourceRoutingScopeLocalOnly: 5,
ResourceRoutingScopeCloudSummary: 6,
ResourceRoutingScopeLocalFirst: 7,
}
if got := ResourcePolicyRoutingSummaryFromCounts(routingCounts); len(got) != 3 || got[0] != "6 Cloud Summary" || got[1] != "7 Local First" || got[2] != "5 Local Only" {
t.Fatalf("routing summary = %#v, want [6 Cloud Summary 7 Local First 5 Local Only]", got)
}
}
func TestResourcePolicySummaryLines(t *testing.T) {
policy := &ResourcePolicy{
Sensitivity: ResourceSensitivityRestricted,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalOnly,
Redact: []ResourceRedactionHint{
ResourceRedactionAlias,
ResourceRedactionHostname,
ResourceRedactionPath,
},
},
}
got := ResourcePolicySummaryLines(policy)
want := []string{
"Policy: sensitivity=Restricted, routing=Local Only",
"Redactions: Hostname, Alias, Path",
}
if len(got) != len(want) {
t.Fatalf("summary lines = %#v, want %#v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("summary line[%d] = %q, want %q", i, got[i], want[i])
}
}
}
func TestResourcePolicyGovernedSummaryGuidance(t *testing.T) {
if got := ResourcePolicyGovernedSummaryPreamble(); got != "Raw hostnames, paths, and local identifiers are withheld when governed resource policy requires redaction." {
t.Fatalf("ResourcePolicyGovernedSummaryPreamble() = %q", got)
}
if got := ResourcePolicyGovernedSummaryFooter(); got != "Raw routing coordinates, bind mounts, hostnames, and discovery file paths withheld by canonical resource policy." {
t.Fatalf("ResourcePolicyGovernedSummaryFooter() = %q", got)
}
}
func TestFormatResourcePolicyGovernedSummary(t *testing.T) {
policy := &ResourcePolicy{
Sensitivity: ResourceSensitivityRestricted,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalOnly,
Redact: []ResourceRedactionHint{
ResourceRedactionHostname,
ResourceRedactionAlias,
},
},
}
got := FormatResourcePolicyGovernedSummary("system container resource; status online; local-only context", policy)
want := "## Governed resource\nsystem container resource; status online; local-only context\nPolicy: sensitivity=Restricted, routing=Local Only\nRedactions: Hostname, Alias\nRaw routing coordinates, bind mounts, hostnames, and discovery file paths withheld by canonical resource policy.\n\n"
if got != want {
t.Fatalf("FormatResourcePolicyGovernedSummary() = %q, want %q", got, want)
}
}
func TestResourcePolicyRedactsAndUsesAISafeSummary(t *testing.T) {
policy := &ResourcePolicy{
Sensitivity: ResourceSensitivitySensitive,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalFirst,
Redact: []ResourceRedactionHint{
ResourceRedactionHostname,
ResourceRedactionIPAddress,
},
},
}
if !ResourcePolicyRedacts(policy, ResourceRedactionHostname) {
t.Fatal("expected hostname to be redacted")
}
if ResourcePolicyRedacts(policy, ResourceRedactionAlias) {
t.Fatal("did not expect alias to be redacted")
}
if !ResourcePolicyUsesAISafeSummary("resource summary safe for remote AI use", policy) {
t.Fatal("expected AI-safe summary to be used")
}
if ResourcePolicyUsesAISafeSummary("", policy) {
t.Fatal("did not expect empty summary to be used")
}
if ResourcePolicyUsesAISafeSummary("resource summary safe for remote AI use", nil) {
t.Fatal("did not expect nil policy to use AI-safe summary")
}
pathOnlyPolicy := &ResourcePolicy{
Sensitivity: ResourceSensitivitySensitive,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalFirst,
Redact: []ResourceRedactionHint{ResourceRedactionPath},
},
}
if !ResourcePolicyUsesAISafeSummary("storage resource; status online; redacted for cloud summary", pathOnlyPolicy) {
t.Fatal("expected AI-safe summary to be used for path-redacted policy")
}
if got := ResourcePolicyLabel(" backup-storage ", " storage resource; status online; redacted for cloud summary ", pathOnlyPolicy); got != "storage resource; status online; redacted for cloud summary" {
t.Fatalf("ResourcePolicyLabel() for path-redacted policy = %q, want governed summary", got)
}
}
func TestResourceAISafeSummaryPolicySuffix(t *testing.T) {
if got := resourceAISafeSummaryPolicySuffix(ResourceSensitivitySensitive); got != "redacted for cloud summary" {
t.Fatalf("sensitive suffix = %q, want redacted for cloud summary", got)
}
if got := resourceAISafeSummaryPolicySuffix(ResourceSensitivityRestricted); got != "local-only context" {
t.Fatalf("restricted suffix = %q, want local-only context", got)
}
if got := resourceAISafeSummaryPolicySuffix(ResourceSensitivityInternal); got != "" {
t.Fatalf("internal suffix = %q, want empty", got)
}
}
func TestResourcePolicyRequiresGovernedSummary(t *testing.T) {
if ResourcePolicyRequiresGovernedSummary(nil) {
t.Fatal("expected nil policy to not require governed summary")
}
if ResourcePolicyRequiresGovernedSummary(&ResourcePolicy{}) {
t.Fatal("expected empty policy to not require governed summary")
}
if !ResourcePolicyRequiresGovernedSummary(&ResourcePolicy{
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalOnly,
},
}) {
t.Fatal("expected local-only policy to require governed summary")
}
if !ResourcePolicyRequiresGovernedSummary(&ResourcePolicy{
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalFirst,
Redact: []ResourceRedactionHint{ResourceRedactionHostname},
},
}) {
t.Fatal("expected redacted policy to require governed summary")
}
}
func TestResourcePolicyLabelHelpers(t *testing.T) {
policy := &ResourcePolicy{
Sensitivity: ResourceSensitivityRestricted,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalOnly,
Redact: []ResourceRedactionHint{
ResourceRedactionHostname,
ResourceRedactionAlias,
},
},
}
if got := ResourcePolicyLabel(" fallback-name ", " governed summary ", policy); got != "governed summary" {
t.Fatalf("ResourcePolicyLabel() = %q, want governed summary", got)
}
if got := ResourcePolicyLabel(" fallback-name ", "", policy); got != ResourcePolicyRedactedLabel {
t.Fatalf("ResourcePolicyLabel() with governed policy and empty summary = %q, want redacted label", got)
}
if got := ResourcePolicyLabel(" fallback-name ", " governed summary ", nil); got != "fallback-name" {
t.Fatalf("ResourcePolicyLabel() with nil policy = %q, want fallback-name", got)
}
if got := ResourcePolicyRedactedValue(" host-01 ", policy, ResourceRedactionHostname, ResourceRedactionAlias); got != ResourcePolicyRedactedLabel {
t.Fatalf("ResourcePolicyRedactedValue() = %q, want %q", got, ResourcePolicyRedactedLabel)
}
if got := ResourcePolicyRedactedValue(" host-01 ", nil, ResourceRedactionHostname); got != "host-01" {
t.Fatalf("ResourcePolicyRedactedValue() with nil policy = %q, want host-01", got)
}
}
func TestResourceRedactionLabelsFromHints(t *testing.T) {
got := ResourceRedactionLabelsFromHints([]ResourceRedactionHint{
ResourceRedactionPath,
ResourceRedactionHostname,
ResourceRedactionHostname,
ResourceRedactionAlias,
})
want := []string{"Hostname", "Alias", "Path"}
if len(got) != len(want) {
t.Fatalf("labels = %#v, want %#v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("labels[%d] = %q, want %q", i, got[i], want[i])
}
}
}
func TestResourceDisplayName(t *testing.T) {
if got := ResourceDisplayName(Resource{
Name: " node-a ",
ID: "id-a",
Canonical: &CanonicalIdentity{
DisplayName: "canonical-node-a",
},
}); got != "canonical-node-a" {
t.Fatalf("ResourceDisplayName() with canonical name = %q, want canonical-node-a", got)
}
if got := ResourceDisplayName(Resource{Name: " node-a ", ID: "id-a"}); got != "node-a" {
t.Fatalf("ResourceDisplayName() with name = %q, want node-a", got)
}
if got := ResourceDisplayName(Resource{Name: " ", ID: " id-b "}); got != "id-b" {
t.Fatalf("ResourceDisplayName() with fallback ID = %q, want id-b", got)
}
if got := ResourceDisplayName(Resource{}); got != "" {
t.Fatalf("ResourceDisplayName() empty resource = %q, want empty string", got)
}
}
func TestCloneResourcePolicy(t *testing.T) {
t.Parallel()
original := &ResourcePolicy{
Sensitivity: ResourceSensitivityRestricted,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeLocalOnly,
Redact: []ResourceRedactionHint{
ResourceRedactionHostname,
ResourceRedactionIPAddress,
},
},
}
clone := CloneResourcePolicy(original)
if clone == nil {
t.Fatal("expected clone")
}
if clone == original {
t.Fatal("expected clone to allocate a new policy")
}
if clone.Sensitivity != original.Sensitivity || clone.Routing.Scope != original.Routing.Scope {
t.Fatalf("clone does not match original: %#v vs %#v", clone, original)
}
if len(clone.Routing.Redact) != len(original.Routing.Redact) {
t.Fatalf("clone redactions = %#v, want %#v", clone.Routing.Redact, original.Routing.Redact)
}
clone.Routing.Redact[0] = ResourceRedactionPath
if original.Routing.Redact[0] != ResourceRedactionHostname {
t.Fatalf("expected clone to deep copy redactions, original mutated to %#v", original.Routing.Redact)
}
if CloneResourcePolicy(nil) != nil {
t.Fatal("expected nil clone for nil policy")
}
}
func TestResourcePolicySummaryLinesOmitRawSignals(t *testing.T) {
got := ResourcePolicySummaryLines(&ResourcePolicy{
Sensitivity: ResourceSensitivityInternal,
Routing: ResourceRoutingPolicy{
Scope: ResourceRoutingScopeCloudSummary,
},
})
joined := strings.Join(got, "\n")
if strings.Contains(joined, "Raw Signals") || strings.Contains(joined, "allowCloudRawSignals") || strings.Contains(joined, "cloud_summary=") {
t.Fatalf("policy summary leaked raw-signals wording: %q", joined)
}
}
func containsRedactionHint(hints []ResourceRedactionHint, want ResourceRedactionHint) bool {
for _, hint := range hints {
if hint == want {
return true
}
}
return false
}