From 04ec74b1f0a7226bfc3c1f9acc8321f02b60cf68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 17 Sep 2023 18:37:29 +0800 Subject: [PATCH 1/2] Bump guardian from 2.3.1 to 2.3.2 (#998) Bumps [guardian](https://github.com/ueberauth/guardian) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/ueberauth/guardian/releases) - [Changelog](https://github.com/ueberauth/guardian/blob/master/CHANGELOG.md) - [Commits](https://github.com/ueberauth/guardian/compare/v2.3.1...v2.3.2) --- updated-dependencies: - dependency-name: guardian dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- mix.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.lock b/mix.lock index 1f991ddc6..138bb4fbd 100644 --- a/mix.lock +++ b/mix.lock @@ -48,14 +48,14 @@ "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.22.2", "6bfca374de34ecc913a28ba391ca184d88d77810a3e427afa8454a71a51341ac", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "8a2d389673aea82d7eae387e6a2ccc12660610080ae7beb19452cfdc1ec30f60"}, "git_hooks": {:hex, :git_hooks, "0.7.3", "09489e94d88dfc767662e22aff2b6208bd7cf555a19dd0e1477cca4683ce0701", [:mix], [{:blankable, "~> 1.0.0", [hex: :blankable, repo: "hexpm", optional: false]}, {:recase, "~> 0.7.0", [hex: :recase, repo: "hexpm", optional: false]}], "hexpm", "d6ddedeb4d3a8602bc3f84e087a38f6150a86d9e790628ed8bc70e6d90681659"}, - "guardian": {:hex, :guardian, "2.3.1", "2b2d78dc399a7df182d739ddc0e566d88723299bfac20be36255e2d052fd215d", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbe241f9ca1b09fad916ad42d6049d2600bbc688aba5b3c4a6c82592a54274c3"}, + "guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"}, "guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"}, "hackney": {:hex, :hackney, "1.18.2", "d7ff544ddae5e1cb49e9cf7fa4e356d7f41b283989a1c304bfc47a8cc1cf966f", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "af94d5c9f97857db257090a4a10e5426ecb6f4918aa5cc666798566ae14b65fd"}, "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inch_ex": {:hex, :inch_ex, "2.1.0-rc.1", "7642a8902c0d2ed5d9b5754b2fc88fedf630500d630fc03db7caca2e92dedb36", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4ceee988760f9382d1c1d0b93ea5875727f6071693e89a0a3c49c456ef1be75d"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"}, + "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, "jsx": {:hex, :jsx, "3.1.0", "d12516baa0bb23a59bb35dccaf02a1bd08243fcbb9efe24f2d9d056ccff71268", [:rebar3], [], "hexpm", "0c5cc8fdc11b53cc25cf65ac6705ad39e54ecc56d1c22e4adb8f5a53fb9427f3"}, "mail": {:hex, :mail, "0.2.3", "2c6bb5f8a5f74845fa50ecd0fb45ea16b164026f285f45104f1c4c078cd616d4", [:mix], [], "hexpm", "932b398fa9c69fdf290d7ff63175826e0f1e24414d5b0763bb00a2acfc6c6bf5"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, From fdeb111ade9c38319bd92488412aced6c03cac6c Mon Sep 17 00:00:00 2001 From: kjw142857 <122250318+kjw142857@users.noreply.github.com> Date: Sun, 17 Sep 2023 20:21:58 +0800 Subject: [PATCH 2/2] Fix issue with contest voting pooling (#1000) --- config/config.exs | 4 +- lib/cadet/assessments/assessments.ex | 223 ++++++++++------- test/cadet/assessments/assessments_test.exs | 258 +++++++++++++++++++- 3 files changed, 387 insertions(+), 98 deletions(-) diff --git a/config/config.exs b/config/config.exs index 244fe1e2e..c998adfe2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -23,7 +23,9 @@ config :cadet, Cadet.Jobs.Scheduler, # Compute contest leaderboard that close in the previous day at 00:01 {"1 0 * * *", {Cadet.Assessments, :update_final_contest_leaderboards, []}}, # Compute rolling leaderboard every 2 hours - {"0 */2 * * *", {Cadet.Assessments, :update_rolling_contest_leaderboards, []}} + {"0 */2 * * *", {Cadet.Assessments, :update_rolling_contest_leaderboards, []}}, + # Collate contest entries that close in the previous day at 00:01 + {"1 0 * * *", {Cadet.Assessments, :update_final_contest_entries, []}} ] # Configures the endpoint diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 1fa79ebc2..57d622ef3 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -23,6 +23,7 @@ defmodule Cadet.Assessments do alias Cadet.ProgramAnalysis.Lexer alias Ecto.Multi alias Cadet.Incentives.Achievements + alias Timex.Duration require Decimal @@ -500,6 +501,35 @@ defmodule Cadet.Assessments do Question.changeset(%Question{}, params_with_assessment_id) end + def update_final_contest_entries do + # 1435 = 1 day - 5 minutes + if Log.log_execution("update_final_contest_entries", Duration.from_minutes(1435)) do + Logger.info("Started update of contest entry pools") + questions = fetch_unassigned_voting_questions() + + for q <- questions do + insert_voting(q.course_id, q.question["contest_number"], q.question_id) + end + + Logger.info("Successfully update contest entry pools") + end + end + + # fetch voting questions where entries have not been assigned + def fetch_unassigned_voting_questions do + voting_assigned_question_ids = + SubmissionVotes + |> select([v], v.question_id) + |> Repo.all() + + Question + |> where(type: :voting) + |> where([q], q.id not in ^voting_assigned_question_ids) + |> join(:inner, [q], asst in assoc(q, :assessment)) + |> select([q, asst], %{course_id: asst.course_id, question: q.question, question_id: q.id}) + |> Repo.all() + end + @doc """ Generates and assigns contest entries for users with given usernames. """ @@ -522,102 +552,119 @@ defmodule Cadet.Assessments do {:error, error_changeset} else - # Returns contest submission ids with answers that contain "return" - contest_submission_ids = - Submission - |> join(:inner, [s], ans in assoc(s, :answers)) - |> join(:inner, [s, ans], cr in assoc(s, :student)) - |> where([s, ans, cr], cr.role == "student") - |> where([s, _], s.assessment_id == ^contest_assessment.id and s.status == "submitted") - |> where( - [_, ans, cr], - fragment( - "?->>'code' like ?", - ans.answer, - "%return%" - ) + if Timex.compare(contest_assessment.close_at, Timex.now()) < 0 do + compile_entries(course_id, contest_assessment, question_id) + else + # contest has not closed, do nothing + {:ok, nil} + end + end + end + + def compile_entries( + course_id, + contest_assessment, + question_id + ) do + # Returns contest submission ids with answers that contain "return" + contest_submission_ids = + Submission + |> join(:inner, [s], ans in assoc(s, :answers)) + |> join(:inner, [s, ans], cr in assoc(s, :student)) + |> where([s, ans, cr], cr.role == "student") + |> where([s, _], s.assessment_id == ^contest_assessment.id and s.status == "submitted") + |> where( + [_, ans, cr], + fragment( + "?->>'code' like ?", + ans.answer, + "%return%" ) - |> select([s, _ans], {s.student_id, s.id}) - |> Repo.all() - |> Enum.into(%{}) + ) + |> select([s, _ans], {s.student_id, s.id}) + |> Repo.all() + |> Enum.into(%{}) - contest_submission_ids_length = Enum.count(contest_submission_ids) + contest_submission_ids_length = Enum.count(contest_submission_ids) - voter_ids = - CourseRegistration - |> where(role: "student", course_id: ^course_id) - |> select([cr], cr.id) - |> Repo.all() + voter_ids = + CourseRegistration + |> where(role: "student", course_id: ^course_id) + |> select([cr], cr.id) + |> Repo.all() - votes_per_user = min(contest_submission_ids_length, 10) + votes_per_user = min(contest_submission_ids_length, 10) - votes_per_submission = - if Enum.empty?(contest_submission_ids) do - 0 - else - trunc(Float.ceil(votes_per_user * length(voter_ids) / contest_submission_ids_length)) - end + votes_per_submission = + if Enum.empty?(contest_submission_ids) do + 0 + else + trunc(Float.ceil(votes_per_user * length(voter_ids) / contest_submission_ids_length)) + end - submission_id_list = - contest_submission_ids - |> Enum.map(fn {_, s_id} -> s_id end) - |> Enum.shuffle() - |> List.duplicate(votes_per_submission) - |> List.flatten() - - {_submission_map, submission_votes_changesets} = - voter_ids - |> Enum.reduce({submission_id_list, []}, fn voter_id, acc -> - {submission_list, submission_votes} = acc - - user_contest_submission_id = Map.get(contest_submission_ids, voter_id) - - {votes, rest} = - submission_list - |> Enum.reduce_while({MapSet.new(), submission_list}, fn s_id, acc -> - {user_votes, submissions} = acc - - max_votes = - if votes_per_user == contest_submission_ids_length and - not is_nil(user_contest_submission_id) do - # no. of submssions is less than 10. Unable to find - votes_per_user - 1 - else - votes_per_user - end - - if MapSet.size(user_votes) < max_votes do - if s_id != user_contest_submission_id and not MapSet.member?(user_votes, s_id) do - new_user_votes = MapSet.put(user_votes, s_id) - new_submissions = List.delete(submissions, s_id) - {:cont, {new_user_votes, new_submissions}} - else - {:cont, {user_votes, submissions}} - end + submission_id_list = + contest_submission_ids + |> Enum.map(fn {_, s_id} -> s_id end) + |> Enum.shuffle() + |> List.duplicate(votes_per_submission) + |> List.flatten() + + {_submission_map, submission_votes_changesets} = + voter_ids + |> Enum.reduce({submission_id_list, []}, fn voter_id, acc -> + {submission_list, submission_votes} = acc + + user_contest_submission_id = Map.get(contest_submission_ids, voter_id) + + {votes, rest} = + submission_list + |> Enum.reduce_while({MapSet.new(), submission_list}, fn s_id, acc -> + {user_votes, submissions} = acc + + max_votes = + if votes_per_user == contest_submission_ids_length and + not is_nil(user_contest_submission_id) do + # no. of submssions is less than 10. Unable to find + votes_per_user - 1 else - {:halt, acc} + votes_per_user end - end) - votes = MapSet.to_list(votes) - - new_submission_votes = - votes - |> Enum.map(fn s_id -> - %SubmissionVotes{voter_id: voter_id, submission_id: s_id, question_id: question_id} - end) - |> Enum.concat(submission_votes) - - {rest, new_submission_votes} - end) - - submission_votes_changesets - |> Enum.with_index() - |> Enum.reduce(Multi.new(), fn {changeset, index}, multi -> - Multi.insert(multi, Integer.to_string(index), changeset) + if MapSet.size(user_votes) < max_votes do + if s_id != user_contest_submission_id and not MapSet.member?(user_votes, s_id) do + new_user_votes = MapSet.put(user_votes, s_id) + new_submissions = List.delete(submissions, s_id) + {:cont, {new_user_votes, new_submissions}} + else + {:cont, {user_votes, submissions}} + end + else + {:halt, acc} + end + end) + + votes = MapSet.to_list(votes) + + new_submission_votes = + votes + |> Enum.map(fn s_id -> + %SubmissionVotes{ + voter_id: voter_id, + submission_id: s_id, + question_id: question_id + } + end) + |> Enum.concat(submission_votes) + + {rest, new_submission_votes} end) - |> Repo.transaction() - end + + submission_votes_changesets + |> Enum.with_index() + |> Enum.reduce(Multi.new(), fn {changeset, index}, multi -> + Multi.insert(multi, Integer.to_string(index), changeset) + end) + |> Repo.transaction() end def update_assessment(id, params) when is_ecto_id(id) do @@ -1026,7 +1073,7 @@ defmodule Cadet.Assessments do """ def update_rolling_contest_leaderboards do # 115 = 2 hours - 5 minutes is default. - if Log.log_execution("update_rolling_contest_leaderboards", Timex.Duration.from_minutes(115)) do + if Log.log_execution("update_rolling_contest_leaderboards", Duration.from_minutes(115)) do Logger.info("Started update_rolling_contest_leaderboards") voting_questions_to_update = fetch_active_voting_questions() @@ -1053,7 +1100,7 @@ defmodule Cadet.Assessments do """ def update_final_contest_leaderboards do # 1435 = 24 hours - 5 minutes - if Log.log_execution("update_final_contest_leaderboards", Timex.Duration.from_minutes(1435)) do + if Log.log_execution("update_final_contest_leaderboards", Duration.from_minutes(1435)) do Logger.info("Started update_final_contest_leaderboards") voting_questions_to_update = fetch_voting_questions_due_yesterday() diff --git a/test/cadet/assessments/assessments_test.exs b/test/cadet/assessments/assessments_test.exs index 319f3aba4..70fe132cd 100644 --- a/test/cadet/assessments/assessments_test.exs +++ b/test/cadet/assessments/assessments_test.exs @@ -147,16 +147,27 @@ defmodule Cadet.AssessmentsTest do end describe "contest voting" do - test "inserts votes into submission_votes table" do - contest_question = insert(:programming_question) - contest_assessment = contest_question.assessment - course = contest_question.assessment.course + test "inserts votes into submission_votes table if contest has closed" do + course = insert(:course) + config = insert(:assessment_config) + # contest assessment that has closed + closed_contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: -1), + course: course, + config: config + ) + + contest_question = insert(:programming_question, assessment: closed_contest_assessment) voting_assessment = insert(:assessment, %{course: course}) question = insert(:voting_question, %{ assessment: voting_assessment, - question: build(:voting_question_content, contest_number: contest_assessment.number) + question: + build(:voting_question_content, contest_number: closed_contest_assessment.number) }) students = @@ -202,7 +213,226 @@ defmodule Cadet.AssessmentsTest do # students with own contest submissions will vote for 5 entries # students without own contest submissin will vote for 6 entries - assert length(Repo.all(SubmissionVotes, question_id: question.id)) == 6 * 5 + 6 + assert SubmissionVotes |> where(question_id: ^question.id) |> Repo.all() |> length() == + 6 * 5 + 6 + end + + test "does not insert entries for voting if contest is still open" do + course = insert(:course) + config = insert(:assessment_config) + # contest assessment that is still open + open_contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: 1), + course: course, + config: config + ) + + contest_question = insert(:programming_question, assessment: open_contest_assessment) + voting_assessment = insert(:assessment, %{course: course}) + + question = + insert(:voting_question, %{ + assessment: voting_assessment, + question: + build(:voting_question_content, contest_number: open_contest_assessment.number) + }) + + students = + insert_list(6, :course_registration, %{ + role: :student, + course: course + }) + + Enum.map(students, fn student -> + submission = + insert(:submission, + student: student, + assessment: contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{code: "return 2;"}, + submission: submission, + question: contest_question + ) + end) + + Assessments.insert_voting(course.id, contest_question.assessment.number, question.id) + + # No entries should be released for students to vote on while the contest is still open + assert SubmissionVotes |> where(question_id: ^question.id) |> Repo.all() |> length() == 0 + end + + test "function that checks for closed contests and releases entries into voting pool" do + course = insert(:course) + config = insert(:assessment_config) + # contest assessment that has closed + closed_contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: -1), + course: course, + config: config + ) + + # contest assessment that is still open + open_contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: 1), + course: course, + config: config + ) + + # contest assessment that is closed but insert_voting has already been done + compiled_contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: -1), + course: course, + config: config + ) + + closed_contest_question = + insert(:programming_question, assessment: closed_contest_assessment) + + open_contest_question = insert(:programming_question, assessment: open_contest_assessment) + + compiled_contest_question = + insert(:programming_question, assessment: compiled_contest_assessment) + + closed_voting_assessment = insert(:assessment, %{course: course}) + open_voting_assessment = insert(:assessment, %{course: course}) + compiled_voting_assessment = insert(:assessment, %{course: course}) + + closed_question = + insert(:voting_question, %{ + assessment: closed_voting_assessment, + question: + build(:voting_question_content, contest_number: closed_contest_assessment.number) + }) + + open_question = + insert(:voting_question, %{ + assessment: open_voting_assessment, + question: + build(:voting_question_content, contest_number: open_contest_assessment.number) + }) + + compiled_question = + insert(:voting_question, %{ + assessment: compiled_voting_assessment, + question: + build(:voting_question_content, contest_number: compiled_contest_assessment.number) + }) + + students = + insert_list(10, :course_registration, %{ + role: :student, + course: course + }) + + first_four = Enum.slice(students, 0..3) + last_six = Enum.slice(students, 4..9) + + Enum.map(first_four, fn student -> + submission = + insert(:submission, + student: student, + assessment: compiled_contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{code: "return 2;"}, + submission: submission, + question: compiled_contest_question + ) + end) + + # Only the compiled_assessment has already released entries into voting pool + Assessments.insert_voting( + course.id, + compiled_contest_question.assessment.number, + compiled_question.id + ) + + assert SubmissionVotes |> where(question_id: ^closed_question.id) |> Repo.all() |> length() == + 0 + + assert SubmissionVotes |> where(question_id: ^open_question.id) |> Repo.all() |> length() == + 0 + + assert SubmissionVotes + |> where(question_id: ^compiled_question.id) + |> Repo.all() + |> length() == 4 * 3 + 6 * 4 + + Enum.map(students, fn student -> + submission = + insert(:submission, + student: student, + assessment: closed_contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{code: "return 2;"}, + submission: submission, + question: closed_contest_question + ) + end) + + Enum.map(students, fn student -> + submission = + insert(:submission, + student: student, + assessment: open_contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{code: "return 2;"}, + submission: submission, + question: open_contest_question + ) + end) + + Enum.map(last_six, fn student -> + submission = + insert(:submission, + student: student, + assessment: compiled_contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{code: "return 2;"}, + submission: submission, + question: compiled_contest_question + ) + end) + + Assessments.update_final_contest_entries() + + # only the closed_contest should have been updated + assert SubmissionVotes |> where(question_id: ^closed_question.id) |> Repo.all() |> length() == + 10 * 9 + + assert SubmissionVotes |> where(question_id: ^open_question.id) |> Repo.all() |> length() == + 0 + + assert SubmissionVotes + |> where(question_id: ^compiled_question.id) + |> Repo.all() + |> length() == 4 * 3 + 6 * 4 end test "create voting parameters with invalid contest number" do @@ -225,9 +455,19 @@ defmodule Cadet.AssessmentsTest do end test "deletes submission_votes when assessment is deleted" do - contest_question = insert(:programming_question) - course = contest_question.assessment.course - config = contest_question.assessment.config + course = insert(:course) + config = insert(:assessment_config) + # contest assessment that has closed + contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: -1), + course: course, + config: config + ) + + contest_question = insert(:programming_question, assessment: contest_assessment) voting_assessment = insert(:assessment, %{course: course, config: config}) question = insert(:voting_question, assessment: voting_assessment) students = insert_list(5, :course_registration, %{role: :student, course: course})