diff --git a/cmd/pulse-sensor-proxy/capabilities_test.go b/cmd/pulse-sensor-proxy/capabilities_test.go new file mode 100644 index 000000000..3553135b4 --- /dev/null +++ b/cmd/pulse-sensor-proxy/capabilities_test.go @@ -0,0 +1,441 @@ +package main + +import ( + "testing" +) + +func TestCapability_Has(t *testing.T) { + tests := []struct { + name string + cap Capability + flag Capability + expected bool + }{ + // Single capability checks + { + name: "read has read", + cap: CapabilityRead, + flag: CapabilityRead, + expected: true, + }, + { + name: "read does not have write", + cap: CapabilityRead, + flag: CapabilityWrite, + expected: false, + }, + { + name: "read does not have admin", + cap: CapabilityRead, + flag: CapabilityAdmin, + expected: false, + }, + { + name: "write has write", + cap: CapabilityWrite, + flag: CapabilityWrite, + expected: true, + }, + { + name: "admin has admin", + cap: CapabilityAdmin, + flag: CapabilityAdmin, + expected: true, + }, + + // Combined capability checks + { + name: "read+write has read", + cap: CapabilityRead | CapabilityWrite, + flag: CapabilityRead, + expected: true, + }, + { + name: "read+write has write", + cap: CapabilityRead | CapabilityWrite, + flag: CapabilityWrite, + expected: true, + }, + { + name: "read+write does not have admin", + cap: CapabilityRead | CapabilityWrite, + flag: CapabilityAdmin, + expected: false, + }, + { + name: "all capabilities has read", + cap: CapabilityRead | CapabilityWrite | CapabilityAdmin, + flag: CapabilityRead, + expected: true, + }, + { + name: "all capabilities has write", + cap: CapabilityRead | CapabilityWrite | CapabilityAdmin, + flag: CapabilityWrite, + expected: true, + }, + { + name: "all capabilities has admin", + cap: CapabilityRead | CapabilityWrite | CapabilityAdmin, + flag: CapabilityAdmin, + expected: true, + }, + + // Zero capability + { + name: "zero capability does not have read", + cap: 0, + flag: CapabilityRead, + expected: false, + }, + { + name: "zero capability does not have write", + cap: 0, + flag: CapabilityWrite, + expected: false, + }, + + // Check for combined flags + { + name: "read+write has read+write combined", + cap: CapabilityRead | CapabilityWrite, + flag: CapabilityRead | CapabilityWrite, + expected: true, + }, + { + name: "read only does not have read+write combined", + cap: CapabilityRead, + flag: CapabilityRead | CapabilityWrite, + expected: false, + }, + + // Legacy all constant + { + name: "legacy all has read", + cap: capabilityLegacyAll, + flag: CapabilityRead, + expected: true, + }, + { + name: "legacy all has write", + cap: capabilityLegacyAll, + flag: CapabilityWrite, + expected: true, + }, + { + name: "legacy all has admin", + cap: capabilityLegacyAll, + flag: CapabilityAdmin, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.cap.Has(tt.flag) + if got != tt.expected { + t.Errorf("Capability(%d).Has(%d) = %v, want %v", tt.cap, tt.flag, got, tt.expected) + } + }) + } +} + +func TestParseCapabilityList(t *testing.T) { + tests := []struct { + name string + input []string + expected Capability + }{ + // Empty/nil input defaults to read + { + name: "nil slice defaults to read", + input: nil, + expected: CapabilityRead, + }, + { + name: "empty slice defaults to read", + input: []string{}, + expected: CapabilityRead, + }, + + // Single capabilities + { + name: "parse read", + input: []string{"read"}, + expected: CapabilityRead, + }, + { + name: "parse write", + input: []string{"write"}, + expected: CapabilityWrite, + }, + { + name: "parse admin", + input: []string{"admin"}, + expected: CapabilityAdmin, + }, + + // Case insensitivity + { + name: "parse READ uppercase", + input: []string{"READ"}, + expected: CapabilityRead, + }, + { + name: "parse Write mixed case", + input: []string{"Write"}, + expected: CapabilityWrite, + }, + { + name: "parse ADMIN uppercase", + input: []string{"ADMIN"}, + expected: CapabilityAdmin, + }, + + // Whitespace handling + { + name: "parse with leading space", + input: []string{" read"}, + expected: CapabilityRead, + }, + { + name: "parse with trailing space", + input: []string{"write "}, + expected: CapabilityWrite, + }, + { + name: "parse with surrounding space", + input: []string{" admin "}, + expected: CapabilityAdmin, + }, + + // Multiple capabilities + { + name: "parse read and write", + input: []string{"read", "write"}, + expected: CapabilityRead | CapabilityWrite, + }, + { + name: "parse read and admin", + input: []string{"read", "admin"}, + expected: CapabilityRead | CapabilityAdmin, + }, + { + name: "parse write and admin", + input: []string{"write", "admin"}, + expected: CapabilityWrite | CapabilityAdmin, + }, + { + name: "parse all three", + input: []string{"read", "write", "admin"}, + expected: CapabilityRead | CapabilityWrite | CapabilityAdmin, + }, + + // Duplicate handling (should still work - OR is idempotent) + { + name: "duplicates ignored", + input: []string{"read", "read", "read"}, + expected: CapabilityRead, + }, + { + name: "duplicates with others", + input: []string{"read", "write", "read"}, + expected: CapabilityRead | CapabilityWrite, + }, + + // Unknown capabilities ignored + { + name: "unknown capability ignored", + input: []string{"unknown"}, + expected: 0, + }, + { + name: "unknown with valid", + input: []string{"read", "unknown", "write"}, + expected: CapabilityRead | CapabilityWrite, + }, + { + name: "empty string ignored", + input: []string{""}, + expected: 0, + }, + { + name: "whitespace only ignored", + input: []string{" "}, + expected: 0, + }, + + // Order independence + { + name: "order admin-write-read", + input: []string{"admin", "write", "read"}, + expected: CapabilityRead | CapabilityWrite | CapabilityAdmin, + }, + + // Mixed case and whitespace combined + { + name: "mixed case and whitespace", + input: []string{" READ ", " Write", "ADMIN "}, + expected: CapabilityRead | CapabilityWrite | CapabilityAdmin, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseCapabilityList(tt.input) + if got != tt.expected { + t.Errorf("parseCapabilityList(%v) = %d, want %d", tt.input, got, tt.expected) + } + }) + } +} + +func TestCapabilityNames(t *testing.T) { + tests := []struct { + name string + cap Capability + expected []string + }{ + // Single capabilities + { + name: "read only", + cap: CapabilityRead, + expected: []string{"read"}, + }, + { + name: "write only", + cap: CapabilityWrite, + expected: []string{"write"}, + }, + { + name: "admin only", + cap: CapabilityAdmin, + expected: []string{"admin"}, + }, + + // Combined capabilities (order matters: read, write, admin) + { + name: "read and write", + cap: CapabilityRead | CapabilityWrite, + expected: []string{"read", "write"}, + }, + { + name: "read and admin", + cap: CapabilityRead | CapabilityAdmin, + expected: []string{"read", "admin"}, + }, + { + name: "write and admin", + cap: CapabilityWrite | CapabilityAdmin, + expected: []string{"write", "admin"}, + }, + { + name: "all three", + cap: CapabilityRead | CapabilityWrite | CapabilityAdmin, + expected: []string{"read", "write", "admin"}, + }, + + // Zero capability + { + name: "zero capability returns empty slice", + cap: 0, + expected: []string{}, + }, + + // Legacy all constant + { + name: "legacy all", + cap: capabilityLegacyAll, + expected: []string{"read", "write", "admin"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := capabilityNames(tt.cap) + + if len(got) != len(tt.expected) { + t.Errorf("capabilityNames(%d) returned %d items, want %d: got %v, want %v", + tt.cap, len(got), len(tt.expected), got, tt.expected) + return + } + + for i, name := range got { + if name != tt.expected[i] { + t.Errorf("capabilityNames(%d)[%d] = %q, want %q", + tt.cap, i, name, tt.expected[i]) + } + } + }) + } +} + +// TestCapabilityRoundTrip verifies that parsing and naming are consistent +func TestCapabilityRoundTrip(t *testing.T) { + tests := []struct { + name string + input []string + expect []string + }{ + { + name: "single read", + input: []string{"read"}, + expect: []string{"read"}, + }, + { + name: "all capabilities", + input: []string{"read", "write", "admin"}, + expect: []string{"read", "write", "admin"}, + }, + { + name: "reverse order normalizes", + input: []string{"admin", "write", "read"}, + expect: []string{"read", "write", "admin"}, + }, + { + name: "duplicates removed", + input: []string{"read", "read", "write"}, + expect: []string{"read", "write"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cap := parseCapabilityList(tt.input) + names := capabilityNames(cap) + + if len(names) != len(tt.expect) { + t.Errorf("round trip %v -> %d -> %v, want %v", + tt.input, cap, names, tt.expect) + return + } + + for i, name := range names { + if name != tt.expect[i] { + t.Errorf("round trip result[%d] = %q, want %q", + i, name, tt.expect[i]) + } + } + }) + } +} + +// TestCapabilityConstants verifies the bit positions are correct +func TestCapabilityConstants(t *testing.T) { + // Verify each capability is a single bit + if CapabilityRead != 1 { + t.Errorf("CapabilityRead = %d, want 1", CapabilityRead) + } + if CapabilityWrite != 2 { + t.Errorf("CapabilityWrite = %d, want 2", CapabilityWrite) + } + if CapabilityAdmin != 4 { + t.Errorf("CapabilityAdmin = %d, want 4", CapabilityAdmin) + } + + // Verify legacy all is the combination of all three + if capabilityLegacyAll != CapabilityRead|CapabilityWrite|CapabilityAdmin { + t.Errorf("capabilityLegacyAll = %d, want %d", + capabilityLegacyAll, CapabilityRead|CapabilityWrite|CapabilityAdmin) + } +}