Repository: casbin/casbin
Pull request #1196 (2023-02-15 10:59:30)

feat: Add methods such as AddPoliciesEx
fix: https://github.com/casbin/casbin/issues/1194

Add the following methods for Enforcer and SyncEnforcer
```golang
AddPoliciesEx(rules [][]string) (bool, error)
AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error)
AddGroupingPoliciesEx(rules [][]string) (bool, error)
AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error)
SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) 
```

The difference between these methods and the methods without the Ex suffix is that if one of the rules already exists, they will continue to check the next rule instead of returning false directly

casbin-bot:  @tangyang9464 @JalinWang please review

Patch:
diff --git a/enforcer_interface.go b/enforcer_interface.go
index bcc1ff8..e1333ee 100644
--- a/enforcer_interface.go
+++ b/enforcer_interface.go
@@ -110,6 +110,8 @@ type IEnforcer interface {
 	AddPolicies(rules [][]string) (bool, error)
 	AddNamedPolicy(ptype string, params ...interface{}) (bool, error)
 	AddNamedPolicies(ptype string, rules [][]string) (bool, error)
+	AddPoliciesEx(rules [][]string) (bool, error)
+	AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error)
 	RemovePolicy(params ...interface{}) (bool, error)
 	RemovePolicies(rules [][]string) (bool, error)
 	RemoveFilteredPolicy(fieldIndex int, fieldValues ...string) (bool, error)
@@ -120,8 +122,10 @@ type IEnforcer interface {
 	HasNamedGroupingPolicy(ptype string, params ...interface{}) bool
 	AddGroupingPolicy(params ...interface{}) (bool, error)
 	AddGroupingPolicies(rules [][]string) (bool, error)
+	AddGroupingPoliciesEx(rules [][]string) (bool, error)
 	AddNamedGroupingPolicy(ptype string, params ...interface{}) (bool, error)
 	AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error)
+	AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error)
 	RemoveGroupingPolicy(params ...interface{}) (bool, error)
 	RemoveGroupingPolicies(rules [][]string) (bool, error)
 	RemoveFilteredGroupingPolicy(fieldIndex int, fieldValues ...string) (bool, error)
@@ -140,6 +144,7 @@ type IEnforcer interface {
 	/* Management API with autoNotifyWatcher disabled */
 	SelfAddPolicy(sec string, ptype string, rule []string) (bool, error)
 	SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error)
+	SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error)
 	SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error)
 	SelfRemovePolicies(sec string, ptype string, rules [][]string) (bool, error)
 	SelfRemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, error)
diff --git a/enforcer_synced.go b/enforcer_synced.go
index 3a9001a..db9de5b 100644
--- a/enforcer_synced.go
+++ b/enforcer_synced.go
@@ -376,6 +376,15 @@ func (e *SyncedEnforcer) AddPolicies(rules [][]string) (bool, error) {
 	return e.Enforcer.AddPolicies(rules)
 }
 
+// AddPoliciesEx adds authorization rules to the current policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddPolicies, other non-existent rules are added instead of returning false directly
+func (e *SyncedEnforcer) AddPoliciesEx(rules [][]string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.AddPoliciesEx(rules)
+}
+
 // AddNamedPolicy adds an authorization rule to the current named policy.
 // If the rule already exists, the function returns false and the rule will not be added.
 // Otherwise the function returns true by adding the new rule.
@@ -394,6 +403,15 @@ func (e *SyncedEnforcer) AddNamedPolicies(ptype string, rules [][]string) (bool,
 	return e.Enforcer.AddNamedPolicies(ptype, rules)
 }
 
+// AddNamedPoliciesEx adds authorization rules to the current named policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly
+func (e *SyncedEnforcer) AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.AddNamedPoliciesEx(ptype, rules)
+}
+
 // RemovePolicy removes an authorization rule from the current policy.
 func (e *SyncedEnforcer) RemovePolicy(params ...interface{}) (bool, error) {
 	e.m.Lock()
@@ -506,6 +524,15 @@ func (e *SyncedEnforcer) AddGroupingPolicies(rules [][]string) (bool, error) {
 	return e.Enforcer.AddGroupingPolicies(rules)
 }
 
+// AddGroupingPoliciesEx adds role inheritance rules to the current policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddGroupingPolicies, other non-existent rules are added instead of returning false directly
+func (e *SyncedEnforcer) AddGroupingPoliciesEx(rules [][]string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.AddGroupingPoliciesEx(rules)
+}
+
 // AddNamedGroupingPolicy adds a named role inheritance rule to the current policy.
 // If the rule already exists, the function returns false and the rule will not be added.
 // Otherwise the function returns true by adding the new rule.
@@ -524,6 +551,15 @@ func (e *SyncedEnforcer) AddNamedGroupingPolicies(ptype string, rules [][]string
 	return e.Enforcer.AddNamedGroupingPolicies(ptype, rules)
 }
 
+// AddNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddNamedGroupingPolicies, other non-existent rules are added instead of returning false directly
+func (e *SyncedEnforcer) AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.AddNamedGroupingPoliciesEx(ptype, rules)
+}
+
 // RemoveGroupingPolicy removes a role inheritance rule from the current policy.
 func (e *SyncedEnforcer) RemoveGroupingPolicy(params ...interface{}) (bool, error) {
 	e.m.Lock()
@@ -609,6 +645,12 @@ func (e *SyncedEnforcer) SelfAddPolicies(sec string, ptype string, rules [][]str
 	return e.Enforcer.SelfAddPolicies(sec, ptype, rules)
 }
 
+func (e *SyncedEnforcer) SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) {
+	e.m.Lock()
+	defer e.m.Unlock()
+	return e.Enforcer.SelfAddPoliciesEx(sec, ptype, rules)
+}
+
 func (e *SyncedEnforcer) SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error) {
 	e.m.Lock()
 	defer e.m.Unlock()
diff --git a/internal_api.go b/internal_api.go
index 8c3f97f..b14e5b4 100644
--- a/internal_api.go
+++ b/internal_api.go
@@ -64,13 +64,15 @@ func (e *Enforcer) addPolicyWithoutNotify(sec string, ptype string, rule []strin
 	return true, nil
 }
 
-// addPolicies adds rules to the current policy.
-func (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules [][]string) (bool, error) {
+// addPoliciesWithoutNotify adds rules to the current policy without notify
+// If autoRemoveRepeat == true, existing rules are automatically filtered
+// Otherwise, false is returned directly
+func (e *Enforcer) addPoliciesWithoutNotify(sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
 	if e.dispatcher != nil && e.autoNotifyDispatcher {
 		return true, e.dispatcher.AddPolicies(sec, ptype, rules)
 	}
 
-	if e.model.HasPolicies(sec, ptype, rules) {
+	if !autoRemoveRepeat && e.model.HasPolicies(sec, ptype, rules) {
 		return false, nil
 	}
 
@@ -321,8 +323,10 @@ func (e *Enforcer) addPolicy(sec string, ptype string, rule []string) (bool, err
 }
 
 // addPolicies adds rules to the current policy.
-func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string) (bool, error) {
-	ok, err := e.addPoliciesWithoutNotify(sec, ptype, rules)
+// If autoRemoveRepeat == true, existing rules are automatically filtered
+// Otherwise, false is returned directly
+func (e *Enforcer) addPolicies(sec string, ptype string, rules [][]string, autoRemoveRepeat bool) (bool, error) {
+	ok, err := e.addPoliciesWithoutNotify(sec, ptype, rules, autoRemoveRepeat)
 	if !ok || err != nil {
 		return ok, err
 	}
diff --git a/management_api.go b/management_api.go
index ef6e570..0797743 100644
--- a/management_api.go
+++ b/management_api.go
@@ -207,6 +207,13 @@ func (e *Enforcer) AddPolicies(rules [][]string) (bool, error) {
 	return e.AddNamedPolicies("p", rules)
 }
 
+// AddPoliciesEx adds authorization rules to the current policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddPolicies, other non-existent rules are added instead of returning false directly
+func (e *Enforcer) AddPoliciesEx(rules [][]string) (bool, error) {
+	return e.AddNamedPoliciesEx("p", rules)
+}
+
 // AddNamedPolicy adds an authorization rule to the current named policy.
 // If the rule already exists, the function returns false and the rule will not be added.
 // Otherwise the function returns true by adding the new rule.
@@ -227,7 +234,14 @@ func (e *Enforcer) AddNamedPolicy(ptype string, params ...interface{}) (bool, er
 // If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.
 // Otherwise the function returns true for the corresponding by adding the new rule.
 func (e *Enforcer) AddNamedPolicies(ptype string, rules [][]string) (bool, error) {
-	return e.addPolicies("p", ptype, rules)
+	return e.addPolicies("p", ptype, rules, false)
+}
+
+// AddNamedPoliciesEx adds authorization rules to the current named policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly
+func (e *Enforcer) AddNamedPoliciesEx(ptype string, rules [][]string) (bool, error) {
+	return e.addPolicies("p", ptype, rules, true)
 }
 
 // RemovePolicy removes an authorization rule from the current policy.
@@ -327,6 +341,13 @@ func (e *Enforcer) AddGroupingPolicies(rules [][]string) (bool, error) {
 	return e.AddNamedGroupingPolicies("g", rules)
 }
 
+// AddGroupingPoliciesEx adds role inheritance rules to the current policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddGroupingPolicies, other non-existent rules are added instead of returning false directly
+func (e *Enforcer) AddGroupingPoliciesEx(rules [][]string) (bool, error) {
+	return e.AddNamedGroupingPoliciesEx("g", rules)
+}
+
 // AddNamedGroupingPolicy adds a named role inheritance rule to the current policy.
 // If the rule already exists, the function returns false and the rule will not be added.
 // Otherwise the function returns true by adding the new rule.
@@ -351,7 +372,14 @@ func (e *Enforcer) AddNamedGroupingPolicy(ptype string, params ...interface{}) (
 // If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.
 // Otherwise the function returns true for the corresponding policy rule by adding the new rule.
 func (e *Enforcer) AddNamedGroupingPolicies(ptype string, rules [][]string) (bool, error) {
-	return e.addPolicies("g", ptype, rules)
+	return e.addPolicies("g", ptype, rules, false)
+}
+
+// AddNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.
+// If the rule already exists, the rule will not be added.
+// But unlike AddNamedGroupingPolicies, other non-existent rules are added instead of returning false directly
+func (e *Enforcer) AddNamedGroupingPoliciesEx(ptype string, rules [][]string) (bool, error) {
+	return e.addPolicies("g", ptype, rules, true)
 }
 
 // RemoveGroupingPolicy removes a role inheritance rule from the current policy.
@@ -424,7 +452,11 @@ func (e *Enforcer) SelfAddPolicy(sec string, ptype string, rule []string) (bool,
 }
 
 func (e *Enforcer) SelfAddPolicies(sec string, ptype string, rules [][]string) (bool, error) {
-	return e.addPoliciesWithoutNotify(sec, ptype, rules)
+	return e.addPoliciesWithoutNotify(sec, ptype, rules, false)
+}
+
+func (e *Enforcer) SelfAddPoliciesEx(sec string, ptype string, rules [][]string) (bool, error) {
+	return e.addPoliciesWithoutNotify(sec, ptype, rules, true)
 }
 
 func (e *Enforcer) SelfRemovePolicy(sec string, ptype string, rule []string) (bool, error) {


Test patch:
diff --git a/enforcer_synced_test.go b/enforcer_synced_test.go
index b597ff2..3704c43 100644
--- a/enforcer_synced_test.go
+++ b/enforcer_synced_test.go
@@ -15,7 +15,9 @@
 package casbin
 
 import (
+	"github.com/casbin/casbin/v2/errors"
 	"github.com/casbin/casbin/v2/util"
+	"sort"
 	"testing"
 	"time"
 )
@@ -131,6 +133,43 @@ func TestSyncedEnforcerSelfAddPolicies(t *testing.T) {
 	}
 }
 
+func TestSyncedEnforcerSelfAddPoliciesEx(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
+		go func() {
+			_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
+		}()
+		go func() {
+			_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}})
+		}()
+		go func() {
+			_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user3", "data3", "read"}, {"user4", "data4", "read"}})
+		}()
+		go func() {
+			_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}})
+		}()
+		go func() {
+			_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
+		}()
+		go func() {
+			_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user6", "data6", "read"}, {"user1", "data1", "read"}})
+		}()
+
+		time.Sleep(100 * time.Millisecond)
+
+		testSyncedEnforcerGetPolicy(t, e, [][]string{
+			{"alice", "data1", "read"},
+			{"bob", "data2", "write"},
+			{"user1", "data1", "read"},
+			{"user2", "data2", "read"},
+			{"user3", "data3", "read"},
+			{"user4", "data4", "read"},
+			{"user5", "data5", "read"},
+			{"user6", "data6", "read"},
+		})
+	}
+}
+
 func TestSyncedEnforcerSelfRemovePolicy(t *testing.T) {
 	for i := 0; i < 10; i++ {
 		e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
@@ -359,3 +398,132 @@ func TestSyncedEnforcerSelfUpdatePolicies(t *testing.T) {
 		})
 	}
 }
+
+func TestSyncedEnforcerAddPoliciesEx(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}}) }()
+		go func() { _, _ = e.AddPoliciesEx([][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}}) }()
+		time.Sleep(100 * time.Millisecond)
+
+		testSyncedEnforcerGetPolicy(t, e, [][]string{
+			{"alice", "data1", "read"},
+			{"bob", "data2", "write"},
+			{"user1", "data1", "read"},
+			{"user2", "data2", "read"},
+			{"user3", "data3", "read"},
+			{"user4", "data4", "read"},
+			{"user5", "data5", "read"},
+			{"user6", "data6", "read"},
+		})
+	}
+}
+
+func TestSyncedEnforcerAddNamedPoliciesEx(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		e, _ := NewSyncedEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user2", "data2", "read"}, {"user3", "data3", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user4", "data4", "read"}, {"user5", "data5", "read"}})
+		}()
+		go func() {
+			_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user5", "data5", "read"}, {"user6", "data6", "read"}})
+		}()
+		time.Sleep(100 * time.Millisecond)
+
+		testSyncedEnforcerGetPolicy(t, e, [][]string{
+			{"alice", "data1", "read"},
+			{"bob", "data2", "write"},
+			{"user1", "data1", "read"},
+			{"user2", "data2", "read"},
+			{"user3", "data3", "read"},
+			{"user4", "data4", "read"},
+			{"user5", "data5", "read"},
+			{"user6", "data6", "read"},
+		})
+	}
+}
+
+func testSyncedEnforcerGetUsers(t *testing.T, e *SyncedEnforcer, res []string, name string, domain ...string) {
+	t.Helper()
+	myRes, err := e.GetUsersForRole(name, domain...)
+	myResCopy := make([]string, len(myRes))
+	copy(myResCopy, myRes)
+	sort.Strings(myRes)
+	sort.Strings(res)
+	switch err {
+	case nil:
+		break
+	case errors.ERR_NAME_NOT_FOUND:
+		t.Log("No name found")
+	default:
+		t.Error("Users for ", name, " could not be fetched: ", err.Error())
+	}
+	t.Log("Users for ", name, ": ", myRes)
+
+	if !util.SetEquals(res, myRes) {
+		t.Error("Users for ", name, ": ", myRes, ", supposed to be ", res)
+	}
+}
+func TestSyncedEnforcerAddGroupingPoliciesEx(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		e, _ := NewSyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
+		e.ClearPolicy()
+
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}, {"user2", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user2", "member"}, {"user3", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user4", "member"}, {"user5", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user5", "member"}, {"user6", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}, {"user2", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user2", "member"}, {"user3", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user4", "member"}, {"user5", "member"}}) }()
+		go func() { _, _ = e.AddGroupingPoliciesEx([][]string{{"user5", "member"}, {"user6", "member"}}) }()
+
+		time.Sleep(100 * time.Millisecond)
+
+		testSyncedEnforcerGetUsers(t, e, []string{"user1", "user2", "user3", "user4", "user5", "user6"}, "member")
+	}
+}
+
+func TestSyncedEnforcerAddNamedGroupingPoliciesEx(t *testing.T) {
+	for i := 0; i < 10; i++ {
+		e, _ := NewSyncedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
+		e.ClearPolicy()
+
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user1", "member"}, {"user2", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user2", "member"}, {"user3", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user4", "member"}, {"user5", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user5", "member"}, {"user6", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user1", "member"}, {"user2", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user2", "member"}, {"user3", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user4", "member"}, {"user5", "member"}}) }()
+		go func() { _, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user5", "member"}, {"user6", "member"}}) }()
+
+		time.Sleep(100 * time.Millisecond)
+
+		testSyncedEnforcerGetUsers(t, e, []string{"user1", "user2", "user3", "user4", "user5", "user6"}, "member")
+	}
+}
diff --git a/management_api_test.go b/management_api_test.go
index 37215fd..a067f49 100644
--- a/management_api_test.go
+++ b/management_api_test.go
@@ -226,6 +226,19 @@ func TestModifyPolicyAPI(t *testing.T) {
 	_, _ = e.UpdatePolicies([][]string{{"eve", "data3", "write"}, {"leyo", "data4", "read"}, {"katy", "data4", "write"}},
 		[][]string{{"eve", "data3", "read"}, {"leyo", "data4", "write"}, {"katy", "data1", "write"}})
 	testGetPolicy(t, e, [][]string{{"eve", "data3", "read"}, {"jack", "data4", "read"}, {"katy", "data1", "write"}, {"leyo", "data4", "write"}, {"ham", "data4", "write"}})
+
+	e.ClearPolicy()
+	_, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user1", "data1", "read"}})
+	testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}})
+	// {"user1", "data1", "read"} repeated
+	_, _ = e.AddPoliciesEx([][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
+	testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}})
+	// {"user1", "data1", "read"}, {"user2", "data2", "read"} repeated
+	_, _ = e.AddNamedPoliciesEx("p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}})
+	testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}})
+	// {"user1", "data1", "read"}, {"user2", "data2", "read"}, , {"user3", "data3", "read"} repeated
+	_, _ = e.SelfAddPoliciesEx("p", "p", [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}, {"user4", "data4", "read"}})
+	testGetPolicy(t, e, [][]string{{"user1", "data1", "read"}, {"user2", "data2", "read"}, {"user3", "data3", "read"}, {"user4", "data4", "read"}})
 }
 
 func TestModifyGroupingPolicyAPI(t *testing.T) {
@@ -300,4 +313,13 @@ func TestModifyGroupingPolicyAPI(t *testing.T) {
 	testGetRoles(t, e, []string{"data5_admin"}, "admin")
 	testGetRoles(t, e, []string{"admin_groups"}, "eve")
 
+	e.ClearPolicy()
+	_, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}})
+	testGetUsers(t, e, []string{"user1"}, "member")
+	// {"user1", "member"} repeated
+	_, _ = e.AddGroupingPoliciesEx([][]string{{"user1", "member"}, {"user2", "member"}})
+	testGetUsers(t, e, []string{"user1", "user2"}, "member")
+	// {"user1", "member"}, {"user2", "member"} repeated
+	_, _ = e.AddNamedGroupingPoliciesEx("g", [][]string{{"user1", "member"}, {"user2", "member"}, {"user3", "member"}})
+	testGetUsers(t, e, []string{"user1", "user2", "user3"}, "member")
 }