commondao_test.gno
11.00 Kb · 430 lines
1package commondao_test
2
3import (
4 "errors"
5 "testing"
6 "time"
7
8 "gno.land/p/nt/uassert/v0"
9 "gno.land/p/nt/urequire/v0"
10
11 "gno.land/p/nt/commondao/v0"
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 TestCommonDAOWithdraw(t *testing.T) {
172 cases := []struct {
173 name string
174 proposalID uint64
175 setup func() *commondao.CommonDAO
176 err error
177 }{
178 {
179 name: "success",
180 proposalID: 1,
181 setup: func() *commondao.CommonDAO {
182 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
183 dao := commondao.New(commondao.WithMember(member))
184 dao.Propose(member, testPropDef{votingPeriod: time.Hour})
185 return dao
186 },
187 },
188 {
189 name: "proposal not found",
190 proposalID: 404,
191 setup: func() *commondao.CommonDAO {
192 return commondao.New(commondao.WithMember("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn"))
193 },
194 err: commondao.ErrProposalNotFound,
195 },
196 {
197 name: "withdrawal not allowed",
198 proposalID: 1,
199 setup: func() *commondao.CommonDAO {
200 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
201 dao := commondao.New(commondao.WithMember(member))
202 p, _ := dao.Propose(member, testPropDef{votingPeriod: time.Hour})
203 dao.Vote(member, p.ID(), commondao.ChoiceYes, "")
204 return dao
205 },
206 err: commondao.ErrWithdrawalNotAllowed,
207 },
208 }
209
210 for _, tc := range cases {
211 t.Run(tc.name, func(t *testing.T) {
212 dao := tc.setup()
213
214 err := dao.Withdraw(tc.proposalID)
215
216 if tc.err != nil {
217 urequire.ErrorIs(t, err, tc.err)
218 return
219 }
220
221 urequire.NoError(t, err)
222 })
223 }
224}
225
226func TestCommonDAOVote(t *testing.T) {
227 cases := []struct {
228 name string
229 setup func() *commondao.CommonDAO
230 member address
231 choice commondao.VoteChoice
232 proposalID uint64
233 err error
234 }{
235 {
236 name: "success",
237 setup: func() *commondao.CommonDAO {
238 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
239 dao := commondao.New(commondao.WithMember(member))
240 dao.Propose(member, testPropDef{votingPeriod: time.Hour})
241 return dao
242 },
243 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
244 choice: commondao.ChoiceYes,
245 proposalID: 1,
246 },
247 {
248 name: "success with custom vote choice",
249 setup: func() *commondao.CommonDAO {
250 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
251 dao := commondao.New(commondao.WithMember(member))
252 dao.Propose(member, testPropDef{
253 votingPeriod: time.Hour,
254 voteChoices: []commondao.VoteChoice{"FOO", "BAR"},
255 })
256 return dao
257 },
258 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
259 choice: commondao.VoteChoice("BAR"),
260 proposalID: 1,
261 },
262 {
263 name: "success with deadline check disabled",
264 setup: func() *commondao.CommonDAO {
265 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
266 dao := commondao.New(
267 commondao.WithMember(member),
268 commondao.DisableVotingDeadlineCheck(),
269 )
270 dao.Propose(member, testPropDef{})
271 return dao
272 },
273 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
274 choice: commondao.ChoiceYes,
275 proposalID: 1,
276 },
277 {
278 name: "invalid vote choice",
279 setup: func() *commondao.CommonDAO {
280 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
281 dao := commondao.New(commondao.WithMember(member))
282 dao.Propose(member, testPropDef{votingPeriod: time.Hour})
283 return dao
284 },
285 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
286 choice: commondao.VoteChoice("invalid"),
287 proposalID: 1,
288 err: commondao.ErrInvalidVoteChoice,
289 },
290 {
291 name: "not a member",
292 setup: func() *commondao.CommonDAO { return commondao.New() },
293 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
294 choice: commondao.ChoiceAbstain,
295 err: commondao.ErrNotMember,
296 },
297 {
298 name: "proposal not found",
299 setup: func() *commondao.CommonDAO {
300 return commondao.New(commondao.WithMember("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn"))
301 },
302 member: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
303 choice: commondao.ChoiceAbstain,
304 proposalID: 42,
305 err: commondao.ErrProposalNotFound,
306 },
307 }
308
309 for _, tc := range cases {
310 t.Run(tc.name, func(t *testing.T) {
311 dao := tc.setup()
312
313 err := dao.Vote(tc.member, tc.proposalID, tc.choice, "")
314
315 if tc.err != nil {
316 urequire.ErrorIs(t, err, tc.err)
317 return
318 }
319
320 urequire.NoError(t, err)
321
322 p := dao.ActiveProposals().Get(tc.proposalID)
323 urequire.NotEqual(t, nil, p, "proposal not found")
324
325 record := p.VotingRecord()
326 uassert.True(t, record.HasVoted(tc.member))
327 uassert.Equal(t, record.VoteCount(tc.choice), 1)
328 })
329 }
330}
331
332func TestCommonDAOExecute(t *testing.T) {
333 errTest := errors.New("test")
334 member := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
335 cases := []struct {
336 name string
337 setup func() *commondao.CommonDAO
338 proposalID uint64
339 status commondao.ProposalStatus
340 statusReason string
341 err error
342 }{
343 {
344 name: "success",
345 setup: func() *commondao.CommonDAO {
346 dao := commondao.New(commondao.WithMember(member))
347 dao.Propose(member, testPropDef{tallyResult: true}) // Non crossing definition
348 return dao
349 },
350 status: commondao.StatusExecuted,
351 proposalID: 1,
352 },
353 {
354 name: "proposal not found",
355 setup: func() *commondao.CommonDAO { return commondao.New() },
356 proposalID: 1,
357 err: commondao.ErrProposalNotFound,
358 },
359 {
360 name: "execution not allowed",
361 setup: func() *commondao.CommonDAO {
362 dao := commondao.New(commondao.WithMember(member))
363 p, _ := dao.Propose(member, testPropDef{tallyResult: false})
364 p.Tally(dao.Members())
365 return dao
366 },
367 proposalID: 1,
368 err: commondao.ErrExecutionNotAllowed,
369 },
370 {
371 name: "voting deadline not met",
372 setup: func() *commondao.CommonDAO {
373 dao := commondao.New(commondao.WithMember(member))
374 dao.Propose(member, testPropDef{votingPeriod: time.Minute * 5})
375 return dao
376 },
377 proposalID: 1,
378 err: commondao.ErrVotingDeadlineNotMet,
379 },
380 {
381 name: "validation error",
382 setup: func() *commondao.CommonDAO {
383 dao := commondao.New(commondao.WithMember(member))
384 dao.Propose(member, testPropDef{
385 validationErr: errTest,
386 tallyResult: true,
387 })
388 return dao
389 },
390 proposalID: 1,
391 status: commondao.StatusFailed,
392 statusReason: errTest.Error(),
393 },
394 {
395 name: "tally error",
396 setup: func() *commondao.CommonDAO {
397 dao := commondao.New(commondao.WithMember(member))
398 dao.Propose(member, testPropDef{tallyErr: errTest})
399 return dao
400 },
401 proposalID: 1,
402 status: commondao.StatusFailed,
403 statusReason: errTest.Error(),
404 },
405 }
406
407 for _, tc := range cases {
408 t.Run(tc.name, func(t *testing.T) {
409 dao := tc.setup()
410
411 err := dao.Execute(tc.proposalID)
412
413 if tc.err != nil {
414 urequire.Error(t, err, "expected an error")
415 urequire.ErrorIs(t, err, tc.err, "expect error to match")
416 return
417 }
418
419 urequire.NoError(t, err, "expect no error")
420
421 found := dao.ActiveProposals().Has(tc.proposalID)
422 urequire.False(t, found, "proposal should not be active")
423
424 p := dao.FinishedProposals().Get(tc.proposalID)
425 urequire.NotEqual(t, nil, p, "proposal must be found")
426 uassert.Equal(t, string(p.Status()), string(tc.status), "status must match")
427 uassert.Equal(t, string(p.StatusReason()), string(tc.statusReason), "status reason must match")
428 })
429 }
430}