Skip to content

Commit b2422a4

Browse files
authored
Prevent deletion of contests when contest voting file is present (#1002)
1 parent fdeb111 commit b2422a4

File tree

2 files changed

+127
-16
lines changed

2 files changed

+127
-16
lines changed

lib/cadet/assessments/assessments.ex

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,33 @@ defmodule Cadet.Assessments do
3636
def delete_assessment(id) do
3737
assessment = Repo.get(Assessment, id)
3838

39-
Submission
40-
|> where(assessment_id: ^id)
41-
|> delete_submission_assocation(id)
39+
is_voted_on =
40+
Question
41+
|> where(type: :voting)
42+
|> join(:inner, [q], asst in assoc(q, :assessment))
43+
|> where(
44+
[q, asst],
45+
q.question["contest_number"] == ^assessment.number and
46+
asst.course_id == ^assessment.course_id
47+
)
48+
|> Repo.exists?()
4249

43-
Question
44-
|> where(assessment_id: ^id)
45-
|> Repo.all()
46-
|> Enum.each(fn q ->
47-
delete_submission_votes_association(q)
48-
end)
50+
if is_voted_on do
51+
{:error, {:bad_request, "Contest voting for this contest is still up"}}
52+
else
53+
Submission
54+
|> where(assessment_id: ^id)
55+
|> delete_submission_assocation(id)
4956

50-
Repo.delete(assessment)
57+
Question
58+
|> where(assessment_id: ^id)
59+
|> Repo.all()
60+
|> Enum.each(fn q ->
61+
delete_submission_votes_association(q)
62+
end)
63+
64+
Repo.delete(assessment)
65+
end
5166
end
5267

5368
defp delete_submission_votes_association(question) do
@@ -522,12 +537,26 @@ defmodule Cadet.Assessments do
522537
|> select([v], v.question_id)
523538
|> Repo.all()
524539

525-
Question
526-
|> where(type: :voting)
527-
|> where([q], q.id not in ^voting_assigned_question_ids)
528-
|> join(:inner, [q], asst in assoc(q, :assessment))
529-
|> select([q, asst], %{course_id: asst.course_id, question: q.question, question_id: q.id})
530-
|> Repo.all()
540+
valid_assessments =
541+
Assessment
542+
|> select([a], %{number: a.number, course_id: a.course_id})
543+
|> Repo.all()
544+
545+
valid_questions =
546+
Question
547+
|> where(type: :voting)
548+
|> where([q], q.id not in ^voting_assigned_question_ids)
549+
|> join(:inner, [q], asst in assoc(q, :assessment))
550+
|> select([q, asst], %{course_id: asst.course_id, question: q.question, question_id: q.id})
551+
|> Repo.all()
552+
553+
# fetch only voting where there is a corresponding contest
554+
Enum.filter(valid_questions, fn q ->
555+
Enum.any?(
556+
valid_assessments,
557+
fn a -> a.number == q.question["contest_number"] and a.course_id == q.course_id end
558+
)
559+
end)
531560
end
532561

533562
@doc """

test/cadet/assessments/assessments_test.exs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ defmodule Cadet.AssessmentsTest do
311311
closed_voting_assessment = insert(:assessment, %{course: course})
312312
open_voting_assessment = insert(:assessment, %{course: course})
313313
compiled_voting_assessment = insert(:assessment, %{course: course})
314+
# voting assessment that references an invalid contest number
315+
invalid_voting_assessment = insert(:assessment, %{course: course})
314316

315317
closed_question =
316318
insert(:voting_question, %{
@@ -333,6 +335,12 @@ defmodule Cadet.AssessmentsTest do
333335
build(:voting_question_content, contest_number: compiled_contest_assessment.number)
334336
})
335337

338+
invalid_question =
339+
insert(:voting_question, %{
340+
assessment: invalid_voting_assessment,
341+
question: build(:voting_question_content, contest_number: "test_invalid")
342+
})
343+
336344
students =
337345
insert_list(10, :course_registration, %{
338346
role: :student,
@@ -375,6 +383,9 @@ defmodule Cadet.AssessmentsTest do
375383
|> Repo.all()
376384
|> length() == 4 * 3 + 6 * 4
377385

386+
assert SubmissionVotes |> where(question_id: ^invalid_question.id) |> Repo.all() |> length() ==
387+
0
388+
378389
Enum.map(students, fn student ->
379390
submission =
380391
insert(:submission,
@@ -420,6 +431,15 @@ defmodule Cadet.AssessmentsTest do
420431
)
421432
end)
422433

434+
# fetching all unassigned voting questions should only yield open and closed questions
435+
unassigned_voting_questions = Assessments.fetch_unassigned_voting_questions()
436+
assert Enum.count(unassigned_voting_questions) == 2
437+
438+
assert Enum.map(unassigned_voting_questions, fn q -> q.question_id end) == [
439+
closed_question.id,
440+
open_question.id
441+
]
442+
423443
Assessments.update_final_contest_entries()
424444

425445
# only the closed_contest should have been updated
@@ -433,6 +453,9 @@ defmodule Cadet.AssessmentsTest do
433453
|> where(question_id: ^compiled_question.id)
434454
|> Repo.all()
435455
|> length() == 4 * 3 + 6 * 4
456+
457+
assert SubmissionVotes |> where(question_id: ^invalid_question.id) |> Repo.all() |> length() ==
458+
0
436459
end
437460

438461
test "create voting parameters with invalid contest number" do
@@ -493,6 +516,65 @@ defmodule Cadet.AssessmentsTest do
493516
Assessments.delete_assessment(voting_assessment.id)
494517
refute Repo.exists?(SubmissionVotes, question_id: question.id)
495518
end
519+
520+
test "does not delete contest assessment if referencing voting assessment is present" do
521+
course = insert(:course)
522+
config = insert(:assessment_config)
523+
524+
contest_assessment =
525+
insert(:assessment,
526+
is_published: true,
527+
open_at: Timex.shift(Timex.now(), days: -5),
528+
close_at: Timex.shift(Timex.now(), hours: -1),
529+
course: course,
530+
config: config,
531+
number: "test"
532+
)
533+
534+
voting_assessment = insert(:assessment, %{course: course, config: config})
535+
536+
# insert voting question that references the contest assessment
537+
_voting_question =
538+
insert(:voting_question, %{
539+
assessment: voting_assessment,
540+
question: build(:voting_question_content, contest_number: contest_assessment.number)
541+
})
542+
543+
error_message = {:bad_request, "Contest voting for this contest is still up"}
544+
545+
assert {:error, ^error_message} = Assessments.delete_assessment(contest_assessment.id)
546+
# deletion should fail
547+
assert Assessment |> where(id: ^contest_assessment.id) |> Repo.exists?()
548+
end
549+
550+
test "deletes contest assessment if voting assessment references same number but different course" do
551+
course_1 = insert(:course)
552+
course_2 = insert(:course)
553+
config = insert(:assessment_config)
554+
555+
contest_assessment =
556+
insert(:assessment,
557+
is_published: true,
558+
open_at: Timex.shift(Timex.now(), days: -5),
559+
close_at: Timex.shift(Timex.now(), hours: -1),
560+
course: course_1,
561+
config: config,
562+
number: "test"
563+
)
564+
565+
voting_assessment = insert(:assessment, %{course: course_2, config: config})
566+
567+
# insert voting question from a different course that references the same contest number
568+
_voting_question =
569+
insert(:voting_question, %{
570+
assessment: voting_assessment,
571+
question: build(:voting_question_content, contest_number: contest_assessment.number)
572+
})
573+
574+
assert {:ok, _} = Assessments.delete_assessment(contest_assessment.id)
575+
# deletion should succeed
576+
refute Assessment |> where(id: ^contest_assessment.id) |> Repo.exists?()
577+
end
496578
end
497579

498580
describe "contest voting leaderboard utility functions" do

0 commit comments

Comments
 (0)