initial draft of hot reload policy
This commit is contained in:
339
pkg/policy/follows_test.go
Normal file
339
pkg/policy/follows_test.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"git.mleku.dev/mleku/nostr/encoders/hex"
|
||||
)
|
||||
|
||||
// setupTestPolicy creates a policy manager with a temporary config file.
|
||||
// Returns the policy and a cleanup function.
|
||||
func setupTestPolicy(t *testing.T, appName string) (*P, func()) {
|
||||
t.Helper()
|
||||
|
||||
// Create config directory at XDG path
|
||||
configDir := filepath.Join(xdg.ConfigHome, appName)
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
t.Fatalf("Failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
// Create default policy.json
|
||||
configPath := filepath.Join(configDir, "policy.json")
|
||||
defaultPolicy := []byte(`{"default_policy": "allow"}`)
|
||||
if err := os.WriteFile(configPath, defaultPolicy, 0644); err != nil {
|
||||
t.Fatalf("Failed to write policy file: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
|
||||
policy := NewWithManager(ctx, appName, true)
|
||||
if policy == nil {
|
||||
cancel()
|
||||
os.RemoveAll(configDir)
|
||||
t.Fatal("Failed to create policy manager")
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
cancel()
|
||||
os.RemoveAll(configDir)
|
||||
}
|
||||
|
||||
return policy, cleanup
|
||||
}
|
||||
|
||||
// TestIsPolicyAdmin tests the IsPolicyAdmin method
|
||||
func TestIsPolicyAdmin(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-policy-admin")
|
||||
defer cleanup()
|
||||
|
||||
// Set up policy with admins
|
||||
admin1Hex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
admin2Hex := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
|
||||
nonAdminHex := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
|
||||
policyJSON := []byte(`{
|
||||
"policy_admins": [
|
||||
"` + admin1Hex + `",
|
||||
"` + admin2Hex + `"
|
||||
]
|
||||
}`)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
|
||||
t.Fatalf("Failed to reload policy: %v", err)
|
||||
}
|
||||
|
||||
// Convert hex to bytes for testing
|
||||
admin1Bin, _ := hex.Dec(admin1Hex)
|
||||
admin2Bin, _ := hex.Dec(admin2Hex)
|
||||
nonAdminBin, _ := hex.Dec(nonAdminHex)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pubkey []byte
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "first admin is recognized",
|
||||
pubkey: admin1Bin,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "second admin is recognized",
|
||||
pubkey: admin2Bin,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "non-admin is not recognized",
|
||||
pubkey: nonAdminBin,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "nil pubkey returns false",
|
||||
pubkey: nil,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty pubkey returns false",
|
||||
pubkey: []byte{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "wrong length pubkey returns false",
|
||||
pubkey: []byte{0x01, 0x02, 0x03},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := policy.IsPolicyAdmin(tt.pubkey)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsPolicyAdmin() = %v, expected %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsPolicyFollow tests the IsPolicyFollow method
|
||||
func TestIsPolicyFollow(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-policy-follow")
|
||||
defer cleanup()
|
||||
|
||||
// Set up some follows
|
||||
follow1Hex := "1111111111111111111111111111111111111111111111111111111111111111"
|
||||
follow2Hex := "2222222222222222222222222222222222222222222222222222222222222222"
|
||||
nonFollowHex := "3333333333333333333333333333333333333333333333333333333333333333"
|
||||
|
||||
follow1Bin, _ := hex.Dec(follow1Hex)
|
||||
follow2Bin, _ := hex.Dec(follow2Hex)
|
||||
nonFollowBin, _ := hex.Dec(nonFollowHex)
|
||||
|
||||
// Update policy follows directly
|
||||
policy.UpdatePolicyFollows([][]byte{follow1Bin, follow2Bin})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pubkey []byte
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "first follow is recognized",
|
||||
pubkey: follow1Bin,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "second follow is recognized",
|
||||
pubkey: follow2Bin,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "non-follow is not recognized",
|
||||
pubkey: nonFollowBin,
|
||||
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.IsPolicyFollow(tt.pubkey)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IsPolicyFollow() = %v, expected %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdatePolicyFollows tests the UpdatePolicyFollows method
|
||||
func TestUpdatePolicyFollows(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-update-follows")
|
||||
defer cleanup()
|
||||
|
||||
// Initially no follows
|
||||
testPubkey, _ := hex.Dec("1111111111111111111111111111111111111111111111111111111111111111")
|
||||
if policy.IsPolicyFollow(testPubkey) {
|
||||
t.Error("Expected no follows initially")
|
||||
}
|
||||
|
||||
// Add follows
|
||||
follows := [][]byte{testPubkey}
|
||||
policy.UpdatePolicyFollows(follows)
|
||||
|
||||
if !policy.IsPolicyFollow(testPubkey) {
|
||||
t.Error("Expected pubkey to be a follow after update")
|
||||
}
|
||||
|
||||
// Update with empty list
|
||||
policy.UpdatePolicyFollows([][]byte{})
|
||||
if policy.IsPolicyFollow(testPubkey) {
|
||||
t.Error("Expected pubkey to not be a follow after clearing")
|
||||
}
|
||||
|
||||
// Update with nil
|
||||
policy.UpdatePolicyFollows(nil)
|
||||
if policy.IsPolicyFollow(testPubkey) {
|
||||
t.Error("Expected pubkey to not be a follow after nil update")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsPolicyFollowWhitelistEnabled tests the IsPolicyFollowWhitelistEnabled method
|
||||
func TestIsPolicyFollowWhitelistEnabled(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-whitelist-enabled")
|
||||
defer cleanup()
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Test with disabled
|
||||
policyJSON := []byte(`{"policy_follow_whitelist_enabled": false}`)
|
||||
if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
|
||||
t.Fatalf("Failed to reload policy: %v", err)
|
||||
}
|
||||
|
||||
if policy.IsPolicyFollowWhitelistEnabled() {
|
||||
t.Error("Expected follow whitelist to be disabled")
|
||||
}
|
||||
|
||||
// Test with enabled
|
||||
policyJSON = []byte(`{"policy_follow_whitelist_enabled": true}`)
|
||||
if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
|
||||
t.Fatalf("Failed to reload policy: %v", err)
|
||||
}
|
||||
|
||||
if !policy.IsPolicyFollowWhitelistEnabled() {
|
||||
t.Error("Expected follow whitelist to be enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPolicyAdminsBin tests the GetPolicyAdminsBin method
|
||||
func TestGetPolicyAdminsBin(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-get-admins-bin")
|
||||
defer cleanup()
|
||||
|
||||
admin1Hex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
admin2Hex := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
|
||||
|
||||
policyJSON := []byte(`{
|
||||
"policy_admins": ["` + admin1Hex + `", "` + admin2Hex + `"]
|
||||
}`)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
|
||||
t.Fatalf("Failed to reload policy: %v", err)
|
||||
}
|
||||
|
||||
admins := policy.GetPolicyAdminsBin()
|
||||
if len(admins) != 2 {
|
||||
t.Errorf("Expected 2 admins, got %d", len(admins))
|
||||
}
|
||||
|
||||
// Verify it's a copy (modification shouldn't affect original)
|
||||
if len(admins) > 0 {
|
||||
admins[0][0] = 0xFF
|
||||
originalAdmins := policy.GetPolicyAdminsBin()
|
||||
if originalAdmins[0][0] == 0xFF {
|
||||
t.Error("GetPolicyAdminsBin should return a copy, not the original slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFollowListConcurrency tests concurrent access to follow list
|
||||
func TestFollowListConcurrency(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-concurrency")
|
||||
defer cleanup()
|
||||
|
||||
testPubkey, _ := hex.Dec("1111111111111111111111111111111111111111111111111111111111111111")
|
||||
|
||||
// Run concurrent reads and writes
|
||||
done := make(chan bool)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
for j := 0; j < 100; j++ {
|
||||
policy.UpdatePolicyFollows([][]byte{testPubkey})
|
||||
_ = policy.IsPolicyFollow(testPubkey)
|
||||
_ = policy.IsPolicyAdmin(testPubkey)
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
// TestPolicyAdminAndFollowInteraction tests the interaction between admin and follow checks
|
||||
func TestPolicyAdminAndFollowInteraction(t *testing.T) {
|
||||
policy, cleanup := setupTestPolicy(t, "test-admin-follow-interaction")
|
||||
defer cleanup()
|
||||
|
||||
// An admin who is also followed
|
||||
adminHex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
||||
adminBin, _ := hex.Dec(adminHex)
|
||||
|
||||
policyJSON := []byte(`{
|
||||
"policy_admins": ["` + adminHex + `"],
|
||||
"policy_follow_whitelist_enabled": true
|
||||
}`)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
|
||||
t.Fatalf("Failed to reload policy: %v", err)
|
||||
}
|
||||
|
||||
// Admin should be recognized as admin
|
||||
if !policy.IsPolicyAdmin(adminBin) {
|
||||
t.Error("Expected admin to be recognized as admin")
|
||||
}
|
||||
|
||||
// Admin is not automatically a follow
|
||||
if policy.IsPolicyFollow(adminBin) {
|
||||
t.Error("Admin should not automatically be a follow")
|
||||
}
|
||||
|
||||
// Now add admin as a follow
|
||||
policy.UpdatePolicyFollows([][]byte{adminBin})
|
||||
|
||||
// Should be both admin and follow
|
||||
if !policy.IsPolicyAdmin(adminBin) {
|
||||
t.Error("Expected admin to still be recognized as admin")
|
||||
}
|
||||
if !policy.IsPolicyFollow(adminBin) {
|
||||
t.Error("Expected admin to now be recognized as follow")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user