Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}