commondao_test.gno
12.28 Kb ยท 464 lines
1package commondao_test
2
3import (
4 "errors"
5 "testing"
6 "time"
7
8 "gno.land/p/nt/uassert"
9 "gno.land/p/nt/urequire"
10
11 "gno.land/p/devx000/wip/nt/commondao"
12)
13
14func TestNew(t *testing.T) {
15 cases := []struct {
16 name string
17 parent *commondao.CommonDAO
18 members []address
19 }{
20 {
21 name: "with parent",
22 parent: commondao.New(),
23 members: []address{"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"},
24 },
25 {
26 name: "without parent",
27 members: []address{"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"},
28 },
29 {
30 name: "multiple members",
31 members: []address{
32 "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
33 "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
34 "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
35 },
36 },
37 {
38 name: "no members",
39 },
40 }
41
42 for _, tc := range cases {
43 t.Run(tc.name, func(t *testing.T) {
44 membersCount := len(tc.members)
45 options := []commondao.Option{commondao.WithParent(tc.parent)}
46 for _, m := range tc.members {
47 options = append(options, commondao.WithMember(m))
48 }
49
50 dao := commondao.New(options...)
51
52 if tc.parent == nil {
53 uassert.Equal(t, nil, dao.Parent())
54 } else {
55 uassert.NotEqual(t, nil, dao.Parent())
56 }
57
58 uassert.False(t, dao.IsDeleted(), "expect DAO not to be soft deleted by default")
59 urequire.Equal(t, membersCount, dao.Members().Size(), "dao members")
60
61 var i int
62 dao.Members().IterateByOffset(0, membersCount, func(addr address) bool {
63 uassert.Equal(t, tc.members[i], addr)
64 i++
65 return false
66 })
67 })
68 }
69}
70
71func TestCommonDAOMembersAdd(t *testing.T) {
72 member := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
73 dao := commondao.New(commondao.WithMember("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn"))
74
75 added := dao.Members().Add(member)
76 urequire.True(t, added)
77
78 uassert.Equal(t, 2, dao.Members().Size())
79 uassert.True(t, dao.Members().Has(member))
80
81 added = dao.Members().Add(member)
82 urequire.False(t, added)
83}
84
85func TestCommonDAOMembersRemove(t *testing.T) {
86 member := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
87 dao := commondao.New(commondao.WithMember(member))
88
89 removed := dao.Members().Remove(member)
90 urequire.True(t, removed)
91
92 removed = dao.Members().Remove(member)
93 urequire.False(t, removed)
94}
95
96func TestCommonDAOMembersHas(t *testing.T) {
97 cases := []struct {
98 name string
99 member address
100 dao *commondao.CommonDAO
101 want bool
102 }{
103 {
104 name: "member",
105 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
106 dao: commondao.New(commondao.WithMember("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")),
107 want: true,
108 },
109 {
110 name: "not a dao member",
111 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
112 dao: commondao.New(commondao.WithMember("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc")),
113 },
114 }
115
116 for _, tc := range cases {
117 t.Run(tc.name, func(t *testing.T) {
118 got := tc.dao.Members().Has(tc.member)
119 uassert.Equal(t, got, tc.want)
120 })
121 }
122}
123
124func TestCommonDAOPropose(t *testing.T) {
125 cases := []struct {
126 name string
127 setup func() *commondao.CommonDAO
128 creator address
129 def commondao.ProposalDefinition
130 err error
131 }{
132 {
133 name: "success",
134 setup: func() *commondao.CommonDAO { return commondao.New() },
135 creator: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
136 def: testPropDef{},
137 },
138 {
139 name: "nil definition",
140 setup: func() *commondao.CommonDAO { return commondao.New() },
141 err: commondao.ErrProposalDefinitionRequired,
142 },
143 {
144 name: "invalid creator address",
145 setup: func() *commondao.CommonDAO { return commondao.New() },
146 def: testPropDef{},
147 err: commondao.ErrInvalidCreatorAddress,
148 },
149 }
150
151 for _, tc := range cases {
152 t.Run(tc.name, func(t *testing.T) {
153 dao := tc.setup()
154
155 p, err := dao.Propose(tc.creator, tc.def)
156
157 if tc.err != nil {
158 urequire.ErrorIs(t, err, tc.err)
159 return
160 }
161
162 urequire.NoError(t, err)
163
164 found := dao.ActiveProposals().Has(p.ID())
165 urequire.True(t, found, "proposal not found")
166 uassert.Equal(t, p.Creator(), tc.creator)
167 })
168 }
169}
170
171func TestCommonDAOVote(t *testing.T) {
172 cases := []struct {
173 name string
174 setup func() *commondao.CommonDAO
175 member address
176 choice commondao.VoteChoice
177 proposalID uint64
178 err error
179 }{
180 {
181 name: "success",
182 setup: func() *commondao.CommonDAO {
183 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
184 dao := commondao.New(commondao.WithMember(member))
185 dao.Propose(member, testPropDef{votingPeriod: time.Hour})
186 return dao
187 },
188 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
189 choice: commondao.ChoiceYes,
190 proposalID: 1,
191 },
192 {
193 name: "success with custom vote choice",
194 setup: func() *commondao.CommonDAO {
195 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
196 dao := commondao.New(commondao.WithMember(member))
197 dao.Propose(member, testPropDef{
198 votingPeriod: time.Hour,
199 voteChoices: []commondao.VoteChoice{"FOO", "BAR"},
200 })
201 return dao
202 },
203 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
204 choice: commondao.VoteChoice("BAR"),
205 proposalID: 1,
206 },
207 {
208 name: "success with deadline check disabled",
209 setup: func() *commondao.CommonDAO {
210 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
211 dao := commondao.New(
212 commondao.WithMember(member),
213 commondao.DisableVotingDeadlineCheck(),
214 )
215 dao.Propose(member, testPropDef{})
216 return dao
217 },
218 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
219 choice: commondao.ChoiceYes,
220 proposalID: 1,
221 },
222 {
223 name: "invalid vote choice",
224 setup: func() *commondao.CommonDAO {
225 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
226 dao := commondao.New(commondao.WithMember(member))
227 dao.Propose(member, testPropDef{votingPeriod: time.Hour})
228 return dao
229 },
230 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
231 choice: commondao.VoteChoice("invalid"),
232 proposalID: 1,
233 err: commondao.ErrInvalidVoteChoice,
234 },
235 {
236 name: "not a member",
237 setup: func() *commondao.CommonDAO { return commondao.New() },
238 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
239 choice: commondao.ChoiceAbstain,
240 err: commondao.ErrNotMember,
241 },
242 {
243 name: "proposal not found",
244 setup: func() *commondao.CommonDAO {
245 return commondao.New(commondao.WithMember("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn"))
246 },
247 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
248 choice: commondao.ChoiceAbstain,
249 proposalID: 42,
250 err: commondao.ErrProposalNotFound,
251 },
252 }
253
254 for _, tc := range cases {
255 t.Run(tc.name, func(t *testing.T) {
256 dao := tc.setup()
257
258 err := dao.Vote(tc.member, tc.proposalID, tc.choice, "")
259
260 if tc.err != nil {
261 urequire.ErrorIs(t, err, tc.err)
262 return
263 }
264
265 urequire.NoError(t, err)
266
267 p := dao.ActiveProposals().Get(tc.proposalID)
268 urequire.NotEqual(t, nil, p, "proposal not found")
269
270 record := p.VotingRecord()
271 uassert.True(t, record.HasVoted(tc.member))
272 uassert.Equal(t, record.VoteCount(tc.choice), 1)
273 })
274 }
275}
276
277func TestCommonDAOTally(t *testing.T) {
278 errTest := errors.New("test")
279 member := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
280 cases := []struct {
281 name string
282 setup func(*commondao.CommonDAO) (proposalID uint64)
283 passes bool
284 err error
285 }{
286 {
287 name: "pass",
288 setup: func(dao *commondao.CommonDAO) uint64 {
289 return dao.MustPropose(member, testPropDef{tallyResult: true}).ID()
290 },
291 passes: true,
292 },
293 {
294 name: "fail to pass",
295 setup: func(dao *commondao.CommonDAO) uint64 {
296 return dao.MustPropose(member, testPropDef{tallyResult: false}).ID()
297 },
298 passes: false,
299 },
300 {
301 name: "proposal not found",
302 setup: func(*commondao.CommonDAO) uint64 { return 404 },
303 err: commondao.ErrProposalNotFound,
304 },
305 // TODO: Requires PR to be merged https://github.com/gnolang/gno/pull/4737
306 // {
307 // name: "proposal status not active",
308 // setup: func(dao *commondao.CommonDAO) uint64 {
309 // p := dao.MustPropose(member, testPropDef{tallyResult: true})
310 // members := commondao.NewMemberSet(dao.Members())
311 // p.Tally(members)
312 // return p.ID()
313 // },
314 // err: commondao.ErrStatusIsNotActive,
315 // },
316 {
317 name: "proposal failed error",
318 setup: func(dao *commondao.CommonDAO) uint64 {
319 return dao.MustPropose(member, testPropDef{tallyErr: commondao.ErrProposalFailed}).ID()
320 },
321 passes: false,
322 },
323 {
324 name: "error",
325 setup: func(dao *commondao.CommonDAO) uint64 {
326 return dao.MustPropose(member, testPropDef{tallyErr: errTest}).ID()
327 },
328 err: errTest,
329 },
330 }
331
332 for _, tc := range cases {
333 t.Run(tc.name, func(t *testing.T) {
334 dao := commondao.New(commondao.WithMember(member))
335 proposalID := tc.setup(dao)
336
337 passes, err := dao.Tally(proposalID)
338
339 if tc.err != nil {
340 uassert.ErrorIs(t, err, tc.err, "expect an error")
341 uassert.False(t, passes, "expect tally to fail")
342 return
343 }
344
345 uassert.NoError(t, err, "expect no error")
346 uassert.Equal(t, tc.passes, passes, "expect tally success value to match")
347 })
348 }
349}
350
351func TestCommonDAOExecute(t *testing.T) {
352 errTest := errors.New("test")
353 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
354 cases := []struct {
355 name string
356 setup func() *commondao.CommonDAO
357 proposalID uint64
358 status commondao.ProposalStatus
359 statusReason string
360 err error
361 }{
362 // TODO: Execution success and error are implemented as filetests
363 // This is done because proposal definition's Execute() must be
364 // crossing which is not possible without defining it within a realm.
365 // {
366 // name: "success",
367 // setup: func() *commondao.CommonDAO {
368 // dao := commondao.New(commondao.WithMember(member))
369 // dao.Propose(member, testPropDef{tallyResult: true})
370 // return dao
371 // },
372 // status: StatusPassed,
373 // proposalID: 1,
374 // },
375 // {
376 // name: "execution error",
377 // setup: func() *commondao.CommonDAO {
378 // dao := commondao.New(commondao.WithMember(member))
379 // dao.Propose(member, testPropDef{
380 // tallyResult: true,
381 // executionErr: errTest,
382 // })
383 // return dao
384 // },
385 // proposalID: 1,
386 // status: StatusFailed,
387 // statusReason: errTest.Error(),
388 // },
389 {
390 name: "proposal not found",
391 setup: func() *commondao.CommonDAO { return commondao.New() },
392 proposalID: 1,
393 err: commondao.ErrProposalNotFound,
394 },
395 // TODO: Requires PR to be merged https://github.com/gnolang/gno/pull/4737
396 // {
397 // name: "proposal not active",
398 // setup: func() *commondao.CommonDAO {
399 // dao := commondao.New(commondao.WithMember(member))
400 // p := dao.MustPropose(member, testPropDef{tallyResult: true})
401 // members := commondao.NewMemberSet(dao.Members())
402 // p.Tally(members)
403 // return dao
404 // },
405 // proposalID: 1,
406 // err: commondao.ErrStatusIsNotActive,
407 // },
408 {
409 name: "voting deadline not met",
410 setup: func() *commondao.CommonDAO {
411 dao := commondao.New(commondao.WithMember(member))
412 dao.Propose(member, testPropDef{votingPeriod: time.Minute * 5})
413 return dao
414 },
415 proposalID: 1,
416 err: commondao.ErrVotingDeadlineNotMet,
417 },
418 {
419 name: "validation error",
420 setup: func() *commondao.CommonDAO {
421 dao := commondao.New(commondao.WithMember(member))
422 dao.Propose(member, testPropDef{validationErr: errTest})
423 return dao
424 },
425 proposalID: 1,
426 status: commondao.StatusFailed,
427 statusReason: errTest.Error(),
428 },
429 {
430 name: "tally error",
431 setup: func() *commondao.CommonDAO {
432 dao := commondao.New(commondao.WithMember(member))
433 dao.Propose(member, testPropDef{tallyErr: errTest})
434 return dao
435 },
436 proposalID: 1,
437 status: commondao.StatusFailed,
438 statusReason: errTest.Error(),
439 },
440 }
441
442 for _, tc := range cases {
443 t.Run(tc.name, func(t *testing.T) {
444 dao := tc.setup()
445
446 err := dao.Execute(tc.proposalID)
447
448 if tc.err != nil {
449 urequire.ErrorIs(t, err, tc.err, "expect error to match")
450 return
451 }
452
453 urequire.NoError(t, err, "expect no error")
454
455 found := dao.ActiveProposals().Has(tc.proposalID)
456 urequire.False(t, found, "proposal should not be active")
457
458 p := dao.FinishedProposals().Get(tc.proposalID)
459 urequire.NotEqual(t, nil, p, "proposal must be found")
460 uassert.Equal(t, string(p.Status()), string(tc.status), "status must match")
461 uassert.Equal(t, string(p.StatusReason()), string(tc.statusReason), "status reason must match")
462 })
463 }
464}