Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}