Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}