commondao_test.gno
11.57 Kb ยท 447 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/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 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 // TODO: Execution success and error are implemented as filetests
344 // This is done because proposal definition's Execute() must be
345 // crossing which is not possible without defining it within a realm.
346 // {
347 // name: "success",
348 // setup: func() *commondao.CommonDAO {
349 // dao := commondao.New(commondao.WithMember(member))
350 // dao.Propose(member, testPropDef{tallyResult: true})
351 // return dao
352 // },
353 // status: StatusPassed,
354 // proposalID: 1,
355 // },
356 // {
357 // name: "execution error",
358 // setup: func() *commondao.CommonDAO {
359 // dao := commondao.New(commondao.WithMember(member))
360 // dao.Propose(member, testPropDef{
361 // tallyResult: true,
362 // executionErr: errTest,
363 // })
364 // return dao
365 // },
366 // proposalID: 1,
367 // status: StatusFailed,
368 // statusReason: errTest.Error(),
369 // },
370 {
371 name: "proposal not found",
372 setup: func() *commondao.CommonDAO { return commondao.New() },
373 proposalID: 1,
374 err: commondao.ErrProposalNotFound,
375 },
376 {
377 name: "execution not allowed",
378 setup: func() *commondao.CommonDAO {
379 dao := commondao.New(commondao.WithMember(member))
380 p, _ := dao.Propose(member, testPropDef{tallyResult: false})
381 p.Tally(dao.Members())
382 return dao
383 },
384 proposalID: 1,
385 err: commondao.ErrExecutionNotAllowed,
386 },
387 {
388 name: "voting deadline not met",
389 setup: func() *commondao.CommonDAO {
390 dao := commondao.New(commondao.WithMember(member))
391 dao.Propose(member, testPropDef{votingPeriod: time.Minute * 5})
392 return dao
393 },
394 proposalID: 1,
395 err: commondao.ErrVotingDeadlineNotMet,
396 },
397 {
398 name: "validation error",
399 setup: func() *commondao.CommonDAO {
400 dao := commondao.New(commondao.WithMember(member))
401 dao.Propose(member, testPropDef{
402 validationErr: errTest,
403 tallyResult: true,
404 })
405 return dao
406 },
407 proposalID: 1,
408 status: commondao.StatusFailed,
409 statusReason: errTest.Error(),
410 },
411 {
412 name: "tally error",
413 setup: func() *commondao.CommonDAO {
414 dao := commondao.New(commondao.WithMember(member))
415 dao.Propose(member, testPropDef{tallyErr: errTest})
416 return dao
417 },
418 proposalID: 1,
419 status: commondao.StatusFailed,
420 statusReason: errTest.Error(),
421 },
422 }
423
424 for _, tc := range cases {
425 t.Run(tc.name, func(t *testing.T) {
426 dao := tc.setup()
427
428 err := dao.Execute(tc.proposalID)
429
430 if tc.err != nil {
431 urequire.Error(t, err, "expected an error")
432 urequire.ErrorIs(t, err, tc.err, "expect error to match")
433 return
434 }
435
436 urequire.NoError(t, err, "expect no error")
437
438 found := dao.ActiveProposals().Has(tc.proposalID)
439 urequire.False(t, found, "proposal should not be active")
440
441 p := dao.FinishedProposals().Get(tc.proposalID)
442 urequire.NotEqual(t, nil, p, "proposal must be found")
443 uassert.Equal(t, string(p.Status()), string(tc.status), "status must match")
444 uassert.Equal(t, string(p.StatusReason()), string(tc.statusReason), "status reason must match")
445 })
446 }
447}