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

source-academy / backend / e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

31 Mar 2025 09:12AM UTC coverage: 19.982% (-73.6%) from 93.607%
e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

Pull #1236

github

RichDom2185
Redate migrations to maintain total ordering
Pull Request #1236: Added Exam mode

12 of 57 new or added lines in 8 files covered. (21.05%)

2430 existing lines in 97 files now uncovered.

671 of 3358 relevant lines covered (19.98%)

3.03 hits per line

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

4.35
/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
2✔
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()
UNCOV
48
    |> validate_xp_adjustment_total()
×
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)
UNCOV
67
    |> validate_xp_adjustment_total()
×
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)
UNCOV
74
    |> validate_xp_adjustment_total()
×
75
  end
76

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

UNCOV
81
    total_xp = answer.xp + answer.xp_adjustment
×
82

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

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

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

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

112
  defp validate_answer_content(changeset) do
UNCOV
113
    validate_arbitrary_embedded_struct_by_type(changeset, :answer, %{
×
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
UNCOV
125
    |> cast(contest_score_param, [:relative_score])
×
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
UNCOV
133
    |> cast(popular_score_param, [:popular_score])
×
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