public void Poll_MultipleChoiceResultsAreRight()
        {
            var poll = new MeetingPoll()
            {
                ClosedAt   = DateTime.UtcNow,
                ParsedData = new PollData()
                {
                    Choices = new Dictionary <int, PollData.PollChoice>()
                    {
                        { 1, new PollData.PollChoice(1, "Name1") },
                        { 2, new PollData.PollChoice(2, "Name2") },
                        { 3, new PollData.PollChoice(3, "Name3") },
                    },
                    MultipleChoiceOption = new PollData.MultipleChoice(),
                },
            };

            var votes = new List <MeetingPollVote>()
            {
                new()
                {
                    IsTiebreaker      = true,
                    VotingPower       = 2,
                    ParsedVoteContent = new PollVoteData()
                    {
                        SelectedOptions = new List <int>()
                        {
                            1, 2
                        },
                    },
                },
                new()
                {
                    ParsedVoteContent = new PollVoteData()
                    {
                        SelectedOptions = new List <int>()
                        {
                            2
                        },
                    },
                },
                new()
                {
                    ParsedVoteContent = new PollVoteData()
                    {
                        SelectedOptions = new List <int>()
                        {
                            3, 2, 1
                        },
                    },
                },
                new()
                {
                    VotingPower       = 2,
                    ParsedVoteContent = new PollVoteData()
                    {
                        SelectedOptions = new List <int>()
                        {
                            3
                        },
                    },
                },
            };

            poll.CalculateResults(votes);

            Assert.NotNull(poll.PollResults);

            var resultData = poll.ParsedResults;

            Assert.NotNull(resultData);
            Assert.NotNull(resultData.Results);
            Assert.Null(resultData.TiebreakInFavourOf);
            Assert.Equal(10, resultData.TotalVotes);

            Assert.Equal(3, resultData.Results.Count);

            // Results check

            Assert.Equal(2, resultData.Results[0].Item1);
            Assert.Equal(4, resultData.Results[0].Item2);

            Assert.Equal(1, resultData.Results[1].Item1);
            Assert.Equal(3, resultData.Results[1].Item2);

            Assert.Equal(3, resultData.Results[2].Item1);
            Assert.Equal(3, resultData.Results[2].Item2);
        }
Пример #2
0
        public async Task <ActionResult <List <MeetingPollDTO> > > VoteInPoll([Required] long id,
                                                                              [Required] long pollId, [Required][FromBody] PollVoteData request)
        {
            var errors = new List <ValidationResult>();

            if (!Validator.TryValidateObject(request, new ValidationContext(request), errors))
            {
                logger.LogError("Poll vote data didn't pass validation:");

                foreach (var error in errors)
                {
                    logger.LogError("Failure: {Error}", error);
                }

                // TODO: send errors to client?
                return(BadRequest("Invalid vote JSON data"));
            }

            if (request.SelectedOptions.GroupBy(i => i).Any(g => g.Count() > 1))
            {
                return(BadRequest("Can't vote multiple times for the same option"));
            }

            var access  = GetCurrentUserAccess();
            var meeting = await GetMeetingWithReadAccess(id, access);

            if (meeting == null)
            {
                return(NotFound());
            }

            var user = HttpContext.AuthenticatedUser() !;

            var member = await GetMeetingMember(meeting.Id, user.Id);

            if (member == null)
            {
                return(this.WorkingForbid("You need to join a meeting before voting in it"));
            }

            var poll = await database.MeetingPolls.FindAsync(id, pollId);

            if (poll == null)
            {
                return(NotFound());
            }

            if (poll.ClosedAt != null)
            {
                return(BadRequest("The poll is closed"));
            }

            var parsedData = poll.ParsedData;

            if (parsedData.WeightedChoices != null)
            {
                if (request.SelectedOptions.Count < 1 && !parsedData.WeightedChoices.CanSelectNone)
                {
                    return(BadRequest("You need to select at least one option"));
                }
            }
            else if (parsedData.MultipleChoiceOption != null)
            {
                var min = parsedData.MultipleChoiceOption.MinimumSelections;
                var max = parsedData.MultipleChoiceOption.MaximumSelections;
                if (request.SelectedOptions.Count < min || request.SelectedOptions.Count > max)
                {
                    return(BadRequest($"This poll requires you to select between {min} to {max} options"));
                }
            }
            else if (parsedData.SingleChoiceOption != null)
            {
                if (request.SelectedOptions.Count > 1)
                {
                    return(BadRequest("This poll allows only a single option to be selected"));
                }

                if (request.SelectedOptions.Count < 1 && parsedData.SingleChoiceOption.CanSelectNone != true)
                {
                    return(BadRequest("You must select at least one option"));
                }
            }

            long?president = null;

            switch (poll.TiebreakType)
            {
            case VotingTiebreakType.President:
                // Fetch this data here before checking if the vote is a duplicate
                president = await GetAssociationPresidentUserId();

                break;
            }

            // Fail if already voted
            if (await GetPollVotingRecord(meeting.Id, poll.PollId, user.Id) != null)
            {
                return(BadRequest("You have already voted in this poll"));
            }

            // Voting power is doubled if person is or has been a board member (as defined in the association rules)
            float votingPower = 1;

            if (user.AssociationMember?.HasBeenBoardMember == true)
            {
                votingPower = 2;
            }

            // These should execute within a single transaction so only one of these can get through
            var votingRecord = new MeetingPollVotingRecord()
            {
                MeetingId = meeting.Id,
                PollId    = poll.PollId,
                UserId    = user.Id,
            };

            var vote = new MeetingPollVote()
            {
                MeetingId         = meeting.Id,
                PollId            = poll.PollId,
                VotingPower       = votingPower,
                ParsedVoteContent = request,
            };

            switch (poll.TiebreakType)
            {
            case VotingTiebreakType.Random:
                // Don't store tiebreak data when it is not needed
                break;

            case VotingTiebreakType.Chairman:
                vote.IsTiebreaker = user.Id == meeting.ChairmanId;
                break;

            case VotingTiebreakType.President:
                // Association's current president is the tiebreaker
                vote.IsTiebreaker = user.Id == president;
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            await database.MeetingPollVotingRecords.AddAsync(votingRecord);

            await database.MeetingPollVotes.AddAsync(vote);

            await database.SaveChangesAsync();

            logger.LogInformation("User {Email} has voted in poll {Id} at {UtcNow}", user.Email, poll.PollId,
                                  DateTime.UtcNow);

            return(Ok());
        }