Files
next.orly.dev/pkg/policy/composition_test.go
mleku 70944d45df
Some checks failed
Go / build-and-release (push) Has been cancelled
Add extensive tests and improve policy configuration handling
Introduce comprehensive tests for policy validation logic, including owner and policy admin scenarios. Update `HandlePolicyConfigUpdate` to differentiate permissions for owners and policy admins, enforcing stricter field restrictions and validation flows.
2025-12-02 07:51:59 +00:00

691 lines
17 KiB
Go

package policy
import (
"encoding/json"
"testing"
)
// TestValidateOwnerPolicyUpdate tests owner-specific validation
func TestValidateOwnerPolicyUpdate(t *testing.T) {
// Create a base policy
basePolicy := &P{
DefaultPolicy: "allow",
Owners: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
PolicyAdmins: []string{"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"},
}
tests := []struct {
name string
newPolicy string
expectError bool
errorMsg string
}{
{
name: "valid owner update with non-empty owners",
newPolicy: `{
"default_policy": "deny",
"owners": ["cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"],
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
}`,
expectError: false,
},
{
name: "invalid - empty owners list",
newPolicy: `{
"default_policy": "deny",
"owners": [],
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
}`,
expectError: true,
errorMsg: "owners list cannot be empty",
},
{
name: "invalid - missing owners field",
newPolicy: `{
"default_policy": "deny",
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"]
}`,
expectError: true,
errorMsg: "owners list cannot be empty",
},
{
name: "invalid - bad owner pubkey format",
newPolicy: `{
"default_policy": "deny",
"owners": ["not-a-valid-pubkey"]
}`,
expectError: true,
errorMsg: "invalid owner pubkey",
},
{
name: "valid - owner can add multiple owners",
newPolicy: `{
"default_policy": "deny",
"owners": [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
]
}`,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := basePolicy.ValidateOwnerPolicyUpdate([]byte(tt.newPolicy))
if tt.expectError {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errorMsg)
} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) {
t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
})
}
}
// TestValidatePolicyAdminUpdate tests policy admin validation
func TestValidatePolicyAdminUpdate(t *testing.T) {
// Create a base policy with known owners and admins
ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
adminPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
allowedPubkey := "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
baseJSON := `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 10000
}
}
}`
basePolicy := &P{}
if err := json.Unmarshal([]byte(baseJSON), basePolicy); err != nil {
t.Fatalf("failed to create base policy: %v", err)
}
adminPubkeyBin := make([]byte, 32)
for i := range adminPubkeyBin {
adminPubkeyBin[i] = 0xbb
}
tests := []struct {
name string
newPolicy string
expectError bool
errorMsg string
}{
{
name: "valid - policy admin can extend write_allow",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `", "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],
"size_limit": 10000
}
}
}`,
expectError: false,
},
{
name: "valid - policy admin can add to kind whitelist",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7, 30023]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: false,
},
{
name: "valid - policy admin can increase size limit",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 20000
}
}
}`,
expectError: false,
},
{
name: "invalid - policy admin cannot modify owners",
newPolicy: `{
"default_policy": "allow",
"owners": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: true,
errorMsg: "cannot modify the 'owners' field",
},
{
name: "invalid - policy admin cannot modify policy_admins",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: true,
errorMsg: "cannot modify the 'policy_admins' field",
},
{
name: "invalid - policy admin cannot remove from kind whitelist",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: true,
errorMsg: "cannot remove kind 7 from whitelist",
},
{
name: "invalid - policy admin cannot remove from write_allow",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": [],
"size_limit": 10000
}
}
}`,
expectError: true,
errorMsg: "cannot remove pubkey",
},
{
name: "invalid - policy admin cannot reduce size limit",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"size_limit": 5000
}
}
}`,
expectError: true,
errorMsg: "cannot reduce size_limit",
},
{
name: "invalid - policy admin cannot remove rule",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {}
}`,
expectError: true,
errorMsg: "cannot remove rule for kind 1",
},
{
name: "valid - policy admin can add blacklist entries for non-admin users",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7],
"blacklist": [4]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"write_deny": ["eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"],
"size_limit": 10000
}
}
}`,
expectError: false,
},
{
name: "invalid - policy admin cannot blacklist owner in write_deny",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"write_deny": ["` + ownerPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: true,
errorMsg: "cannot blacklist owner",
},
{
name: "invalid - policy admin cannot blacklist other policy admin",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"write_deny": ["` + adminPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: true,
errorMsg: "cannot blacklist policy admin",
},
{
name: "valid - policy admin can blacklist whitelisted non-admin user",
newPolicy: `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"],
"policy_admins": ["` + adminPubkey + `"],
"kind": {
"whitelist": [1, 3, 7]
},
"rules": {
"1": {
"description": "Text notes",
"write_allow": ["` + allowedPubkey + `"],
"write_deny": ["` + allowedPubkey + `"],
"size_limit": 10000
}
}
}`,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := basePolicy.ValidatePolicyAdminUpdate([]byte(tt.newPolicy), adminPubkeyBin)
if tt.expectError {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errorMsg)
} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) {
t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
})
}
}
// TestIsOwner tests the IsOwner method
func TestIsOwner(t *testing.T) {
ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
nonOwnerPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
_ = nonOwnerPubkey // Silence unused variable warning
policyJSON := `{
"default_policy": "allow",
"owners": ["` + ownerPubkey + `"]
}`
policy, err := New([]byte(policyJSON))
if err != nil {
t.Fatalf("failed to create policy: %v", err)
}
// Create binary pubkeys
ownerBin := make([]byte, 32)
for i := range ownerBin {
ownerBin[i] = 0xaa
}
nonOwnerBin := make([]byte, 32)
for i := range nonOwnerBin {
nonOwnerBin[i] = 0xbb
}
tests := []struct {
name string
pubkey []byte
expected bool
}{
{
name: "owner is recognized",
pubkey: ownerBin,
expected: true,
},
{
name: "non-owner is not recognized",
pubkey: nonOwnerBin,
expected: false,
},
{
name: "nil pubkey returns false",
pubkey: nil,
expected: false,
},
{
name: "empty pubkey returns false",
pubkey: []byte{},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := policy.IsOwner(tt.pubkey)
if result != tt.expected {
t.Errorf("expected %v, got %v", tt.expected, result)
}
})
}
}
// TestStringSliceEqual tests the helper function
func TestStringSliceEqual(t *testing.T) {
tests := []struct {
name string
a []string
b []string
expected bool
}{
{
name: "equal slices same order",
a: []string{"a", "b", "c"},
b: []string{"a", "b", "c"},
expected: true,
},
{
name: "equal slices different order",
a: []string{"a", "b", "c"},
b: []string{"c", "a", "b"},
expected: true,
},
{
name: "different lengths",
a: []string{"a", "b"},
b: []string{"a", "b", "c"},
expected: false,
},
{
name: "different contents",
a: []string{"a", "b", "c"},
b: []string{"a", "b", "d"},
expected: false,
},
{
name: "empty slices",
a: []string{},
b: []string{},
expected: true,
},
{
name: "nil slices",
a: nil,
b: nil,
expected: true,
},
{
name: "nil vs empty",
a: nil,
b: []string{},
expected: true,
},
{
name: "duplicates in both",
a: []string{"a", "a", "b"},
b: []string{"a", "b", "a"},
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := stringSliceEqual(tt.a, tt.b)
if result != tt.expected {
t.Errorf("expected %v, got %v", tt.expected, result)
}
})
}
}
// TestPolicyAdminContributionValidation tests the contribution validation
func TestPolicyAdminContributionValidation(t *testing.T) {
ownerPubkey := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
adminPubkey := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
ownerPolicy := &P{
DefaultPolicy: "allow",
Owners: []string{ownerPubkey},
PolicyAdmins: []string{adminPubkey},
Kind: Kinds{
Whitelist: []int{1, 3, 7},
},
rules: map[int]Rule{
1: {
Description: "Text notes",
SizeLimit: ptr(int64(10000)),
},
},
}
tests := []struct {
name string
contribution *PolicyAdminContribution
expectError bool
errorMsg string
}{
{
name: "valid - add kinds to whitelist",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
KindWhitelistAdd: []int{30023},
},
expectError: false,
},
{
name: "valid - add to blacklist",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
KindBlacklistAdd: []int{4},
},
expectError: false,
},
{
name: "valid - extend existing rule with larger limit",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
RulesExtend: map[int]RuleExtension{
1: {
SizeLimitOverride: ptr(int64(20000)),
},
},
},
expectError: false,
},
{
name: "invalid - extend non-existent rule",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
RulesExtend: map[int]RuleExtension{
999: {
SizeLimitOverride: ptr(int64(20000)),
},
},
},
expectError: true,
errorMsg: "cannot extend rule for kind 999",
},
{
name: "invalid - size limit override smaller than owner's",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
RulesExtend: map[int]RuleExtension{
1: {
SizeLimitOverride: ptr(int64(5000)),
},
},
},
expectError: true,
errorMsg: "size_limit_override for kind 1 must be >=",
},
{
name: "valid - add new rule for undefined kind",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
RulesAdd: map[int]Rule{
30023: {
Description: "Long-form content",
SizeLimit: ptr(int64(100000)),
},
},
},
expectError: false,
},
{
name: "invalid - add rule for already-defined kind",
contribution: &PolicyAdminContribution{
AdminPubkey: adminPubkey,
CreatedAt: 1234567890,
EventID: "event123",
RulesAdd: map[int]Rule{
1: {
Description: "Trying to override",
},
},
},
expectError: true,
errorMsg: "cannot add rule for kind 1: already defined",
},
{
name: "invalid - bad pubkey length in extension",
contribution: &PolicyAdminContribution{
AdminPubkey: "short",
CreatedAt: 1234567890,
EventID: "event123",
},
expectError: true,
errorMsg: "invalid admin pubkey length",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidatePolicyAdminContribution(ownerPolicy, tt.contribution, nil)
if tt.expectError {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errorMsg)
} else if tt.errorMsg != "" && !containsSubstring(err.Error(), tt.errorMsg) {
t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
}
})
}
}
// Helper function for generic pointer
func ptr[T any](v T) *T {
return &v
}