proposal_test.gno

8.85 Kb ยท 330 lines
  1package commondao
  2
  3import (
  4	"errors"
  5	"std"
  6	"testing"
  7	"time"
  8
  9	"gno.land/p/nt/uassert"
 10	"gno.land/p/nt/urequire"
 11)
 12
 13func TestProposalNew(t *testing.T) {
 14	cases := []struct {
 15		name       string
 16		creator    std.Address
 17		definition ProposalDefinition
 18		err        error
 19	}{
 20		{
 21			name:       "success",
 22			creator:    "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
 23			definition: testPropDef{votingPeriod: time.Minute * 10},
 24		},
 25		{
 26			name:       "invalid creator address",
 27			creator:    "invalid",
 28			definition: testPropDef{},
 29			err:        ErrInvalidCreatorAddress,
 30		},
 31		{
 32			name:    "max custom vote choices exceeded",
 33			creator: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
 34			definition: testPropDef{
 35				voteChoices: make([]VoteChoice, MaxCustomVoteChoices+1),
 36			},
 37			err: ErrMaxCustomVoteChoices,
 38		},
 39	}
 40
 41	for _, tc := range cases {
 42		t.Run(tc.name, func(t *testing.T) {
 43			id := uint64(1)
 44
 45			p, err := NewProposal(id, tc.creator, tc.definition)
 46
 47			if tc.err != nil {
 48				urequire.ErrorIs(t, err, tc.err, "expected an error")
 49				return
 50			}
 51
 52			urequire.NoError(t, err, "unexpected error")
 53			uassert.Equal(t, p.ID(), id)
 54			uassert.NotEqual(t, p.Definition(), nil)
 55			uassert.True(t, p.Status() == StatusActive)
 56			uassert.Equal(t, p.Creator(), tc.creator)
 57			uassert.False(t, p.CreatedAt().IsZero())
 58			uassert.NotEqual(t, p.VotingRecord(), nil)
 59			uassert.Empty(t, p.StatusReason())
 60			uassert.True(t, p.VotingDeadline() == p.CreatedAt().Add(tc.definition.VotingPeriod()))
 61		})
 62	}
 63}
 64
 65func TestProposalVoteChoices(t *testing.T) {
 66	cases := []struct {
 67		name       string
 68		definition ProposalDefinition
 69		choices    []VoteChoice
 70	}{
 71		{
 72			name:       "custom choices",
 73			definition: testPropDef{voteChoices: []VoteChoice{"FOO", "BAR", "BAZ"}},
 74			choices:    []VoteChoice{"BAR", "BAZ", "FOO"},
 75		},
 76		{
 77			name:       "defaults because of empty custom choice list",
 78			definition: testPropDef{voteChoices: []VoteChoice{}},
 79			choices:    []VoteChoice{ChoiceAbstain, ChoiceNo, ChoiceYes},
 80		},
 81		{
 82			name:       "defaults because of single custom choice list",
 83			definition: testPropDef{voteChoices: []VoteChoice{"FOO"}},
 84			choices:    []VoteChoice{ChoiceAbstain, ChoiceNo, ChoiceYes},
 85		},
 86	}
 87
 88	for _, tc := range cases {
 89		t.Run(tc.name, func(t *testing.T) {
 90			p, _ := NewProposal(1, "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", testPropDef{
 91				voteChoices: tc.choices,
 92			})
 93
 94			choices := p.VoteChoices()
 95
 96			urequire.Equal(t, len(choices), len(tc.choices), "expect vote choice count to match")
 97			for i, c := range choices {
 98				urequire.True(t, tc.choices[i] == c, "expect vote choice to match")
 99			}
100		})
101	}
102}
103
104func TestIsQuorumReached(t *testing.T) {
105	cases := []struct {
106		name    string
107		quorum  float64
108		members []std.Address
109		votes   []Vote
110		fail    bool
111	}{
112		{
113			name:   "one third",
114			quorum: QuorumOneThird,
115			members: []std.Address{
116				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
117				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
118				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
119			},
120			votes: []Vote{
121				{Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", Choice: ChoiceYes},
122			},
123		},
124		{
125			name:   "one third no quorum",
126			quorum: QuorumOneThird,
127			members: []std.Address{
128				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
129				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
130				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
131			},
132			fail: true,
133		},
134		{
135			name:   "half",
136			quorum: QuorumHalf,
137			members: []std.Address{
138				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
139				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
140				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
141				"g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
142			},
143			votes: []Vote{
144				{Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Choice: ChoiceYes},
145				{Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", Choice: ChoiceNo},
146			},
147		},
148		{
149			name:   "half no quorum",
150			quorum: QuorumHalf,
151			members: []std.Address{
152				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
153				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
154				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
155				"g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
156			},
157			votes: []Vote{
158				{Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", Choice: ChoiceYes},
159			},
160			fail: true,
161		},
162		{
163			name:   "two thirds",
164			quorum: QuorumTwoThirds,
165			members: []std.Address{
166				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
167				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
168				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
169			},
170			votes: []Vote{
171				{Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Choice: ChoiceYes},
172				{Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", Choice: ChoiceNo},
173			},
174		},
175		{
176			name:   "two thirds no quorum",
177			quorum: QuorumTwoThirds,
178			members: []std.Address{
179				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
180				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
181				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
182			},
183			votes: []Vote{
184				{Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", Choice: ChoiceNo},
185			},
186			fail: true,
187		},
188		{
189			name:   "three fourths",
190			quorum: QuorumThreeFourths,
191			members: []std.Address{
192				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
193				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
194				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
195				"g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
196			},
197			votes: []Vote{
198				{Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Choice: ChoiceYes},
199				{Address: "g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", Choice: ChoiceNo},
200				{Address: "g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt", Choice: ChoiceNo},
201			},
202		},
203		{
204			name:   "three fourths no quorum",
205			quorum: QuorumThreeFourths,
206			members: []std.Address{
207				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
208				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
209				"g1w4ek2u3jta047h6lta047h6lta047h6l9huexc",
210				"g125t352u4pmdrr57emc4pe04y40sknr5ztng5mt",
211			},
212			votes: []Vote{
213				{Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Choice: ChoiceYes},
214			},
215			fail: true,
216		},
217		{
218			name:   "full",
219			quorum: QuorumFull,
220			members: []std.Address{
221				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
222				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
223			},
224			votes: []Vote{
225				{Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Choice: ChoiceNo},
226				{Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", Choice: ChoiceNo},
227			},
228		},
229		{
230			name:   "full no quorum",
231			quorum: QuorumFull,
232			members: []std.Address{
233				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
234				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
235			},
236			votes: []Vote{
237				{Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", Choice: ChoiceNo},
238			},
239			fail: true,
240		},
241		{
242			name:   "no quorum with empty vote",
243			quorum: QuorumHalf,
244			members: []std.Address{
245				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
246				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
247			},
248			votes: []Vote{
249				{Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", Choice: ChoiceNone},
250			},
251			fail: true,
252		},
253		{
254			name:   "no quorum with abstention",
255			quorum: QuorumHalf,
256			members: []std.Address{
257				"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
258				"g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
259			},
260			votes: []Vote{
261				{Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", Choice: ChoiceAbstain},
262			},
263			fail: true,
264		},
265		{
266			name:   "invalid quorum percentage",
267			quorum: -1,
268			fail:   true,
269		},
270	}
271
272	for _, tc := range cases {
273		t.Run(tc.name, func(t *testing.T) {
274			members := NewMemberStorage()
275			for _, m := range tc.members {
276				members.Add(m)
277			}
278
279			var record VotingRecord
280			for _, v := range tc.votes {
281				record.AddVote(v)
282			}
283
284			success := IsQuorumReached(tc.quorum, record.Readonly(), NewMemberSet(members))
285
286			if tc.fail {
287				uassert.False(t, success, "expect quorum to fail")
288			} else {
289				uassert.True(t, success, "expect quorum to succeed")
290			}
291		})
292	}
293}
294
295func TestMustValidate(t *testing.T) {
296	uassert.NotPanics(t, func() {
297		MustValidate(testPropDef{})
298	}, "expect validation to succeed")
299
300	uassert.PanicsWithMessage(t, "validable proposal definition is nil", func() {
301		MustValidate(nil)
302	}, "expect validation to panic with nil definition")
303
304	uassert.PanicsWithMessage(t, "boom!", func() {
305		MustValidate(testPropDef{validationErr: errors.New("boom!")})
306	}, "expect validation to panic")
307}
308
309type testPropDef struct {
310	votingPeriod            time.Duration
311	tallyResult             bool
312	validationErr, tallyErr error
313	voteChoices             []VoteChoice
314}
315
316func (testPropDef) Title() string                 { return "" }
317func (testPropDef) Body() string                  { return "" }
318func (d testPropDef) VotingPeriod() time.Duration { return d.votingPeriod }
319func (d testPropDef) Validate() error             { return d.validationErr }
320
321func (d testPropDef) Tally(ReadonlyVotingRecord, MemberSet) (bool, error) {
322	return d.tallyResult, d.tallyErr
323}
324
325func (d testPropDef) CustomVoteChoices() []VoteChoice {
326	if len(d.voteChoices) > 0 {
327		return d.voteChoices
328	}
329	return []VoteChoice{ChoiceYes, ChoiceNo, ChoiceAbstain}
330}