• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / backend / 18dc689a4df4836fc6967bf0f74dc252964bd175-PR-1180

08 Sep 2024 06:14PM UTC coverage: 79.088% (-15.3%) from 94.372%
18dc689a4df4836fc6967bf0f74dc252964bd175-PR-1180

Pull #1180

github

josh1248
Change appropriate routes into admin scope
Pull Request #1180: Transfer groundControl (and admin panel) from staff to admin route

7 of 12 new or added lines in 1 file covered. (58.33%)

499 existing lines in 25 files now uncovered.

2602 of 3290 relevant lines covered (79.09%)

1023.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/lib/cadet/assessments/answer.ex
1
defmodule Cadet.Assessments.Answer do
2
  @moduledoc """
3
  Answers model contains domain logic for answers management for
4
  programming and multiple choice questions.
5
  """
6
  use Cadet, :model
7

8
  alias Cadet.Repo
9
  alias Cadet.Accounts.CourseRegistration
10
  alias Cadet.Assessments.Answer.AutogradingStatus
11
  alias Cadet.Assessments.AnswerTypes.{MCQAnswer, ProgrammingAnswer, VotingAnswer}
12
  alias Cadet.Assessments.{Question, QuestionType, Submission}
13

14
  @type t :: %__MODULE__{}
15

16
  schema "answers" do
138,594✔
17
    # used to compare answers with others
18
    field(:relative_score, :float, default: 0.0)
19
    field(:popular_score, :float, default: 0.0)
20
    field(:xp, :integer, default: 0)
21
    field(:xp_adjustment, :integer, default: 0)
22
    field(:comments, :string)
23
    field(:autograding_status, AutogradingStatus, default: :none)
24
    field(:autograding_results, {:array, :map}, default: [])
25
    field(:answer, :map)
26
    field(:type, QuestionType, virtual: true)
27
    field(:last_modified_at, :utc_datetime_usec)
28

29
    belongs_to(:grader, CourseRegistration)
30
    belongs_to(:submission, Submission)
31
    belongs_to(:question, Question)
32

33
    timestamps()
34
  end
35

36
  @required_fields ~w(answer submission_id question_id type)a
37
  @optional_fields ~w(xp xp_adjustment grader_id comments last_modified_at)a
38

39
  def changeset(answer, params) do
40
    answer
41
    |> cast(params, @required_fields ++ @optional_fields)
42
    |> add_belongs_to_id_from_model([:submission, :question], params)
43
    |> add_question_type_from_model(params)
44
    |> validate_required(@required_fields)
45
    |> foreign_key_constraint(:submission_id)
46
    |> foreign_key_constraint(:question_id)
47
    |> validate_answer_content()
48
    |> validate_xp_adjustment_total()
126✔
49
  end
50

51
  @spec grading_changeset(%__MODULE__{} | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
52
  def grading_changeset(answer, params) do
53
    answer
54
    |> cast(
55
      params,
56
      ~w(
57
        grader_id
58
        xp
59
        xp_adjustment
60
        autograding_results
61
        autograding_status
62
        comments
63
      )a
64
    )
65
    |> add_belongs_to_id_from_model(:grader, params)
66
    |> foreign_key_constraint(:grader_id)
67
    |> validate_xp_adjustment_total()
5✔
68
  end
69

70
  @spec autograding_changeset(%__MODULE__{} | Ecto.Changeset.t(), map()) :: Ecto.Changeset.t()
71
  def autograding_changeset(answer, params) do
72
    answer
73
    |> cast(params, ~w(xp xp_adjustment autograding_status autograding_results)a)
74
    |> validate_xp_adjustment_total()
138✔
75
  end
76

77
  @spec validate_xp_adjustment_total(Ecto.Changeset.t()) :: Ecto.Changeset.t()
78
  defp validate_xp_adjustment_total(changeset) do
79
    answer = apply_changes(changeset)
269✔
80

81
    total_xp = answer.xp + answer.xp_adjustment
269✔
82

83
    with {:question_id, question_id} when is_ecto_id(question_id) <-
3✔
84
           {:question_id, answer.question_id},
269✔
85
         {:question, %{max_xp: max_xp}} <-
267✔
86
           {:question, Repo.get(Question, question_id)},
87
         {:total_xp, true} <- {:total_xp, total_xp >= 0 and total_xp <= max_xp} do
266✔
88
      changeset
266✔
89
    else
90
      {:question_id, _} ->
91
        add_error(changeset, :question_id, "is required")
2✔
92

93
      {:question, _} ->
94
        add_error(changeset, :question_id, "refers to non-existent question")
1✔
95

96
      {:total_xp, false} ->
UNCOV
97
        add_error(changeset, :xp_adjustment, "must make total be between 0 and question.max_xp")
×
98
    end
99
    |> validate_number(:xp, greater_than_or_equal_to: 0)
269✔
100
  end
101

102
  defp add_question_type_from_model(changeset, params) do
103
    with question when is_map(question) <- Map.get(params, :question),
126✔
104
         nil <- get_change(changeset, :type),
1✔
105
         type when is_atom(type) <- Map.get(question, :type) do
1✔
106
      change(changeset, %{type: type})
1✔
107
    else
108
      _ -> changeset
125✔
109
    end
110
  end
111

112
  defp validate_answer_content(changeset) do
113
    validate_arbitrary_embedded_struct_by_type(changeset, :answer, %{
126✔
114
      mcq: MCQAnswer,
115
      programming: ProgrammingAnswer,
116
      voting: VotingAnswer
117
    })
118
  end
119

120
  @doc """
121
  Used to update relative_score of answer to contest_score
122
  """
123
  def contest_score_update_changeset(answer, contest_score_param) do
124
    answer
125
    |> cast(contest_score_param, [:relative_score])
15✔
126
  end
127

128
  @doc """
129
  Used to update popular_score of answer to contest_score
130
  """
131
  def popular_score_update_changeset(answer, popular_score_param) do
132
    answer
133
    |> cast(popular_score_param, [:popular_score])
15✔
134
  end
135
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc