Skip to content

Commit

Permalink
Refactor and add test `failure: a vote should not include an answer v…
Browse files Browse the repository at this point in the history
…alue that is not present in the ballot`.
  • Loading branch information
zorn committed Jun 25, 2024
1 parent 0db9fa6 commit 2b5c963
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 16 deletions.
7 changes: 5 additions & 2 deletions lib/flick/votes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ defmodule Flick.Votes do
a specific `Flick.Ballots.Ballot`.
"""

alias Flick.Ballots.Ballot
alias Flick.Repo
alias Flick.Votes.Vote

Expand All @@ -12,8 +13,10 @@ defmodule Flick.Votes do
@doc """
Records a vote for the given `Flick.Ballots.Ballot` entity.
"""
@spec record_vote(map()) :: {:ok, Vote.t()} | {:error, changeset()}
def record_vote(attrs) do
@spec record_vote(Ballot.t(), map()) :: {:ok, Vote.t()} | {:error, changeset()}
def record_vote(ballot, attrs) do
attrs = Map.put(attrs, "ballot_id", ballot.id)

%Vote{}
|> Vote.changeset(attrs)
|> Repo.insert()
Expand Down
32 changes: 32 additions & 0 deletions lib/flick/votes/vote.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Flick.Votes.Vote do
use Ecto.Schema

import Ecto.Changeset
import FlickWeb.Gettext

alias Flick.Ballots.Ballot
alias Flick.Votes.Answer
Expand Down Expand Up @@ -46,9 +47,40 @@ defmodule Flick.Votes.Vote do
drop_param: :answers_drop,
required: true
)
|> validate_answers_are_present_in_ballot()
|> validate_answers_question_uniqueness()
end

defp validate_answers_are_present_in_ballot(changeset) do
validate_change(changeset, :answers, fn :answers, new_answers ->
ballot = Flick.Ballots.get_ballot!(get_field(changeset, :ballot_id))
# For each of the answers, make sure each ranked answer is present in the
# possible answers for the question from the ballot.
Enum.reduce(new_answers, [], fn changeset, acc ->
question_id = get_field(changeset, :question_id)
ranked_answers = get_field(changeset, :ranked_answers)
question_from_ballot = Enum.find(ballot.questions, &(&1.id == question_id))

case question_from_ballot do
nil ->
[answers: "question_id not found in ballot"]

question ->
possible_answers = String.split(question.possible_answers, ",")
invalid_answers = Enum.reject(ranked_answers, &Enum.member?(possible_answers, &1))

if invalid_answers == [] do
acc
else
error_label = ngettext("invalid answer", "invalid answers", length(invalid_answers))
error_description = Enum.join(invalid_answers, ", ")
[answers: "#{error_label}: #{error_description}"]
end
end
end)
end)
end

defp validate_answers_question_uniqueness(changeset) do
validate_change(changeset, :answers, fn :answers, new_answers ->
question_ids =
Expand Down
35 changes: 21 additions & 14 deletions test/flick/votes_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ defmodule Flick.VotesTest do
question_id = hd(published_ballot.questions).id

assert {:ok, vote} =
Votes.record_vote(%{
"ballot_id" => published_ballot_id,
Votes.record_vote(published_ballot, %{
"answers" => [
%{
"question_id" => question_id,
Expand All @@ -59,8 +58,7 @@ defmodule Flick.VotesTest do
question_id = hd(published_ballot.questions).id

assert {:ok, vote} =
Votes.record_vote(%{
"ballot_id" => published_ballot_id,
Votes.record_vote(published_ballot, %{
"answers" => [
%{
"question_id" => question_id,
Expand All @@ -83,11 +81,8 @@ defmodule Flick.VotesTest do
test "failure: a vote must answer all ballot questions", %{
published_ballot: published_ballot
} do
published_ballot_id = published_ballot.id

assert {:error, changeset} =
Votes.record_vote(%{
"ballot_id" => published_ballot_id,
Votes.record_vote(published_ballot, %{
"answers" => []
})

Expand All @@ -97,11 +92,8 @@ defmodule Flick.VotesTest do
test "failure: a vote can only have a single answer per ballot question", %{
published_ballot: published_ballot
} do
published_ballot_id = published_ballot.id

assert {:error, changeset} =
Votes.record_vote(%{
"ballot_id" => published_ballot_id,
Votes.record_vote(published_ballot, %{
"answers" => [
%{
"question_id" => hd(published_ballot.questions).id,
Expand All @@ -117,7 +109,22 @@ defmodule Flick.VotesTest do
assert "should not include duplicate question ids" in errors_on(changeset).answers
end

# test "failure: a answer must align to a know answer option of the ballot" do
# end
test "failure: a vote should not include an answer value that is not present in the ballot",
%{
published_ballot: published_ballot
} do
attrs = %{
"answers" => [
%{
"question_id" => hd(published_ballot.questions).id,
"ranked_answers" => ["Forbidden Hot Dogs", "Illegal Cookies"]
}
]
}

assert {:error, changeset} = Votes.record_vote(published_ballot, attrs)

assert "invalid answers: Forbidden Hot Dogs, Illegal Cookies" in errors_on(changeset).answers
end
end
end

0 comments on commit 2b5c963

Please sign in to comment.