/ src / lib / utils / rpgf / validate-ballot.unit.test.ts
validate-ballot.unit.test.ts
 1  import { describe, expect, it } from 'vitest';
 2  import type { InProgressBallot } from './types/ballot';
 3  import type { Round } from './types/round';
 4  import { ballotValidationErrors, prepareBallotForSubmission } from './validate-ballot';
 5  
 6  const roundConstraints = {
 7    minVotesPerProjectPerVoter: 10,
 8    maxVotesPerProjectPerVoter: 100,
 9  } satisfies Pick<Round, 'minVotesPerProjectPerVoter' | 'maxVotesPerProjectPerVoter'>;
10  
11  describe('prepareBallotForSubmission', () => {
12    it('returns a sanitized ballot when all entries meet constraints', () => {
13      const ballot: InProgressBallot = {
14        'app-1': 20,
15        'app-2': '15',
16        'app-3': null,
17      };
18  
19      expect(prepareBallotForSubmission(ballot, roundConstraints)).toEqual({
20        'app-1': 20,
21        'app-2': 15,
22      });
23    });
24  
25    it('throws when a vote falls below the round minimum', () => {
26      const ballot: InProgressBallot = {
27        'app-1': 5,
28      };
29  
30      expect(() => prepareBallotForSubmission(ballot, roundConstraints)).toThrow(
31        'Invalid ballot: votes per project must be at least 10.',
32      );
33    });
34  
35    it('throws when a vote exceeds the round maximum', () => {
36      const ballot: InProgressBallot = {
37        'app-1': 150,
38      };
39  
40      expect(() => prepareBallotForSubmission(ballot, roundConstraints)).toThrow(
41        'Invalid ballot: votes per project must not exceed 100.',
42      );
43    });
44  
45    it('throws when votes are non-positive integers', () => {
46      const ballotZero: InProgressBallot = {
47        'app-1': 0,
48      };
49  
50      expect(() => prepareBallotForSubmission(ballotZero, roundConstraints)).toThrow(
51        ballotValidationErrors.POSITIVE_INTEGER_ERROR,
52      );
53  
54      const ballotNegative: InProgressBallot = {
55        'app-1': -5,
56      };
57  
58      expect(() => prepareBallotForSubmission(ballotNegative, roundConstraints)).toThrow(
59        ballotValidationErrors.POSITIVE_INTEGER_ERROR,
60      );
61    });
62  
63    it('allows submissions when no minimum is configured', () => {
64      const roundWithoutMin = {
65        ...roundConstraints,
66        minVotesPerProjectPerVoter: null,
67      } satisfies Pick<Round, 'minVotesPerProjectPerVoter' | 'maxVotesPerProjectPerVoter'>;
68  
69      const ballot: InProgressBallot = {
70        'app-1': 1,
71      };
72  
73      expect(prepareBallotForSubmission(ballot, roundWithoutMin)).toEqual({
74        'app-1': 1,
75      });
76    });
77  });