Search Apps Documentation Source Content File Folder Download Copy Actions Download

daocond_test.gno

6.01 Kb ยท 269 lines
  1package daocond_test
  2
  3import (
  4	"errors"
  5	"testing"
  6
  7	"gno.land/p/nt/urequire"
  8	"gno.land/p/samcrew/daocond"
  9)
 10
 11func TestCondition(t *testing.T) {
 12	dao := newMockDAO()
 13
 14	// leaf conditions
 15	membersMajority := daocond.MembersThreshold(0.6, dao.isMember, dao.membersCount)
 16	publicRelationships := daocond.RoleCount(1, "public-relationships", dao.hasRole)
 17	financeOfficer := daocond.RoleCount(1, "finance-officer", dao.hasRole)
 18
 19	urequire.Equal(t, "60% of members", membersMajority.Render())
 20	urequire.Equal(t, "1 public-relationships", publicRelationships.Render())
 21	urequire.Equal(t, "1 finance-officer", financeOfficer.Render())
 22
 23	// ressource expressions
 24	ressources := map[string]daocond.Condition{
 25		"social.post":    daocond.And(publicRelationships, membersMajority),
 26		"finance.invest": daocond.Or(financeOfficer, membersMajority),
 27	}
 28
 29	urequire.Equal(t, "[1 public-relationships AND 60% of members]", ressources["social.post"].Render())
 30	urequire.Equal(t, "[1 finance-officer OR 60% of members]", ressources["finance.invest"].Render())
 31}
 32
 33func TestEval(t *testing.T) {
 34	setups := []struct {
 35		name  string
 36		setup func(dao *mockDAO)
 37	}{
 38		{name: "basic", setup: func(dao *mockDAO) {
 39			membersMajority := daocond.MembersThreshold(0.6, dao.isMember, dao.membersCount)
 40			publicRelationships := daocond.RoleCount(1, "public-relationships", dao.hasRole)
 41			financeOfficer := daocond.RoleCount(1, "finance-officer", dao.hasRole)
 42			dao.resources = map[string]daocond.Condition{
 43				"social.post":    daocond.And(publicRelationships, membersMajority),
 44				"finance.invest": daocond.Or(financeOfficer, membersMajority),
 45			}
 46		}},
 47	}
 48
 49	cases := []struct {
 50		name     string
 51		resource string
 52		phases   []testPhase
 53	}{
 54		{
 55			name:     "post with public-relationships",
 56			resource: "social.post",
 57			phases: []testPhase{{
 58				votes: map[string]daocond.Vote{
 59					"alice": "yes",
 60					"bob":   "yes",
 61					"eve":   "no",
 62				},
 63				result: true,
 64			}},
 65		},
 66		{
 67			name:     "post without public-relationships",
 68			resource: "social.post",
 69			phases: []testPhase{{
 70				votes: map[string]daocond.Vote{
 71					"alice": "yes",
 72					"bob":   "no",
 73					"eve":   "yes",
 74				},
 75				result: false,
 76			}},
 77		},
 78		{
 79			name:     "post after public-relationships changes",
 80			resource: "social.post",
 81			phases: []testPhase{
 82				{
 83					votes: map[string]daocond.Vote{
 84						"alice": "yes",
 85						"bob":   "yes",
 86						"eve":   "no",
 87					},
 88					result: true,
 89				},
 90				{
 91					changes: func(dao *mockDAO) {
 92						dao.unassignRole("bob", "public-relationships")
 93					},
 94					result: false,
 95				},
 96				{
 97					changes: func(dao *mockDAO) {
 98						dao.assignRole("alice", "public-relationships")
 99					},
100					result: true,
101				},
102			},
103		},
104		{
105			name:     "post public-relationships alone",
106			resource: "social.post",
107			phases: []testPhase{{
108				votes: map[string]daocond.Vote{
109					"alice": "no",
110					"bob":   "yes",
111					"eve":   "no",
112				},
113				result: false,
114			}},
115		},
116		{
117			name:     "invest with finance officer",
118			resource: "finance.invest",
119			phases: []testPhase{{
120				votes: map[string]daocond.Vote{
121					"alice": "yes",
122					"bob":   "no",
123					"eve":   "no",
124				},
125				result: true,
126			}},
127		},
128		{
129			name:     "invest without finance officer",
130			resource: "finance.invest",
131			phases: []testPhase{{
132				votes: map[string]daocond.Vote{
133					"alice": "no",
134					"bob":   "yes",
135					"eve":   "yes",
136				},
137				result: true,
138			}},
139		},
140		{
141			name:     "invest alone",
142			resource: "finance.invest",
143			phases: []testPhase{{
144				votes: map[string]daocond.Vote{
145					"alice": "no",
146					"bob":   "no",
147					"eve":   "yes",
148				},
149				result: false,
150			}},
151		},
152	}
153
154	for _, tc := range cases {
155		for _, s := range setups {
156			t.Run(tc.name+" "+s.name, func(t *testing.T) {
157				dao := newMockDAO()
158				s.setup(dao)
159
160				resource, ok := dao.resources[tc.resource]
161				urequire.True(t, ok)
162
163				ballot := daocond.NewBallot()
164				for _, phase := range tc.phases {
165					if phase.changes != nil {
166						phase.changes(dao)
167					}
168					if phase.votes != nil {
169						for memberId, vote := range phase.votes {
170							ballot.Vote(memberId, vote)
171						}
172					}
173					result := resource.Eval(ballot)
174					if phase.result != result {
175						println("State:", resource.RenderWithVotes(ballot))
176					}
177					urequire.Equal(t, phase.result, result)
178				}
179			})
180		}
181	}
182}
183
184type testPhase struct {
185	changes func(dao *mockDAO)
186	votes   map[string]daocond.Vote
187	result  bool
188}
189
190type mockDAO struct {
191	members   map[string][]string
192	roles     map[string][]string
193	resources map[string]daocond.Condition
194}
195
196func newMockDAO() *mockDAO {
197	return &mockDAO{
198		members: map[string][]string{
199			"alice": []string{"finance-officer"},
200			"bob":   []string{"public-relationships"},
201			"eve":   []string{},
202		},
203		roles: map[string][]string{
204			"finance-officer":      []string{"alice"},
205			"public-relationships": []string{"bob"},
206		}, // roles to users
207		resources: make(map[string]daocond.Condition),
208	}
209}
210
211func (m *mockDAO) assignRole(userId string, role string) {
212	roles, ok := m.members[userId]
213	if !ok {
214		panic(errors.New("unknown member"))
215	}
216	m.members[userId], ok = strsadd(roles, role)
217}
218
219func (m *mockDAO) unassignRole(userId string, role string) {
220	roles, ok := m.members[userId]
221	if !ok {
222		panic(errors.New("unknown member"))
223	}
224	m.members[userId], ok = strsrm(roles, role)
225}
226
227func (m *mockDAO) isMember(memberId string) bool {
228	_, ok := m.members[memberId]
229	return ok
230}
231
232func (m *mockDAO) membersCount() uint64 {
233	return uint64(len(m.members))
234}
235
236func (m *mockDAO) hasRole(memberId string, role string) bool {
237	roles, ok := m.members[memberId]
238	if !ok {
239		return false
240	}
241	for _, memberRole := range roles {
242		if memberRole == role {
243			return true
244		}
245	}
246	return false
247}
248
249func strsrm(strs []string, val string) ([]string, bool) {
250	removed := false
251	res := []string{}
252	for _, str := range strs {
253		if str == val {
254			removed = true
255			continue
256		}
257		res = append(res, str)
258	}
259	return res, removed
260}
261
262func strsadd(strs []string, val string) ([]string, bool) {
263	for _, str := range strs {
264		if str == val {
265			return strs, false
266		}
267	}
268	return append(strs, val), true
269}