canvas-lms/spec/factories/quiz_factory.rb

360 lines
19 KiB
Ruby
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# coding: utf-8
#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module Factories
def quiz_model(opts={})
@context ||= opts.delete(:course) || course_model(:reusable => true)
@quiz = @context.quizzes.build(valid_quiz_attributes.merge(opts))
@quiz.published_at = Time.zone.now
@quiz.workflow_state = 'available'
@quiz.save!
@quiz
end
def valid_quiz_attributes
{
:title => "Test Quiz",
:description => "Test Quiz Description"
}
end
def quiz_with_submission(complete_quiz = true, skip_submission = false)
@course ||= course_model(:reusable => true)
@student ||= user_model
@course.enroll_student(@student).accept
@quiz = @course.quizzes.create
@quiz.workflow_state = "available"
@quiz.quiz_questions.create!({ question_data: test_quiz_data.first })
@quiz.save!
return if skip_submission
@qsub = Quizzes::SubmissionManager.new(@quiz).find_or_create_submission(@student)
@qsub.quiz_data = test_quiz_data
@qsub.started_at = 1.minute.ago
@qsub.attempt = 1
if complete_quiz
@qsub.submission_data = [{:points=>0, :text=>"7051", :question_id=>128, :correct=>false, :answer_id=>7051}]
@qsub.score = 0
@qsub.finished_at = Time.now.utc
@qsub.workflow_state = 'complete'
@qsub.submission = @quiz.assignment.find_or_create_submission(@student.id)
else
@qsub.submission_data = {}
end
@qsub.with_versioning(true) do
@qsub.save!
end
@qsub
end
def test_quiz_data
[
{
correct_comments: '',
assessment_question_id: nil,
incorrect_comments: '',
question_name: 'Question 1',
points_possible: 1,
question_text: 'Which book(s) are required for this course?',
name: 'Question 1',
id: 128,
answers: [
{ weight: 100, text: 'A', comments: '', id: 1490 },
{ weight: 0, text: 'B', comments: '', id: 1020 },
{ weight: 0, text: 'C', comments: '', id: 7051 }
],
question_type: 'multiple_choice_question'
}
]
end
def generate_quiz(course)
quiz = course.quizzes.create(workflow_state: 'available')
quiz.quiz_questions.create!(question_data: test_quiz_data().first)
quiz.save!
quiz
end
def generate_quiz_submission(quiz, student:, finished_at: nil)
qsub = Quizzes::SubmissionManager.new(quiz).find_or_create_submission(student)
qsub.quiz_data = test_quiz_data()
qsub.started_at = 1.minute.ago
qsub.attempt = 1
if finished_at.nil?
qsub.submission_data = {}
else
qsub.submission_data = [{ points: 0, text: "7051", question_id: 128, correct: false, answer_id: 7051 }]
qsub.score = 0
qsub.finished_at = finished_at || Time.now.utc
qsub.workflow_state = 'complete'
end
qsub.submission = quiz.assignment.find_or_create_submission(student.id)
qsub.submission.quiz_submission = qsub
qsub.submission.submission_type = 'online_quiz'
qsub.submission.submitted_at = qsub.finished_at
qsub.save!
qsub
end
def multiple_choice_question_data
{"name"=>"Question", "correct_comments"=>"", "question_type"=>"multiple_choice_question", "assessment_question_id"=>4, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>50.0, "answers"=>[{"comments"=>"", "weight"=>0, "text"=>"a", "id"=>2405}, {"comments"=>"", "weight"=>0, "text"=>"b", "id"=>8544}, {"comments"=>"", "weight"=>100, "text"=>"c", "id"=>1658}, {"comments"=>"", "weight"=>0, "text"=>"d", "id"=>2903}], "question_text"=>"<p>test</p>", "id" => 1}.with_indifferent_access
end
def true_false_question_data
{"name"=>"Question", "correct_comments"=>"", "question_type"=>"true_false_question", "assessment_question_id"=>8197062, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>45, "answers"=>[{"comments"=>"", "weight"=>0, "text"=>"True", "id"=>8403}, {"comments"=>"", "weight"=>100, "text"=>"False", "id"=>8950}], "question_text"=>"<p>test</p>", "id" => 1}.with_indifferent_access
end
def short_answer_question_data
{"name"=>"Question", "correct_comments"=>"", "question_type"=>"short_answer_question", "assessment_question_id"=>8197062, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>16.5, "answers"=>[{"comments"=>"", "weight"=>100, "text"=>"stupid", "id"=>7100}, {"comments"=>"", "weight"=>100, "text"=>"dumb", "id"=>2159}], "question_text"=>"<p>there's no such thing as a _____ question</p>", "id" => 1}.with_indifferent_access
end
def short_answer_question_data_one_blank
{"name"=>"Question", "correct_comments"=>"", "question_type"=>"short_answer_question", "assessment_question_id"=>8197062, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>16.5, "answers"=>[{"comments"=>"", "weight"=>100, "text"=>"stupid", "id"=>7100}, {"comments"=>"", "weight"=>100, "text"=>"dumb", "id"=>2159}, {"comments"=>"", "weight"=>100, "text"=>"", "id"=>9090}], "question_text"=>"<p>there's no such thing as a _____ question</p>", "id" => 1}.with_indifferent_access
end
def essay_question_data
{"name"=>"Question", "correct_comments"=>"", "question_type"=>"essay_question", "comments"=>nil, "assessment_question_id"=>8197062, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>13.6, "answers"=>[], "question_text"=>"<p>Please summarize the history of the world in 3 sentences</p>", "id" => 1}.with_indifferent_access
end
def text_only_question_data
{ "question_type" => "text_only_question", "id" => 3 }.with_indifferent_access
end
def multiple_dropdowns_question_data
{"position"=>3, "correct_comments"=>"", "name"=>"Question 3", "question_type"=>"multiple_dropdowns_question", "assessment_question_id"=>1695442, "neutral_comments"=>"", "incorrect_comments"=>"", "id"=>1630873, "question_name"=>"Question 3", "points_possible"=>0.5, "original_question_text"=>"[structure1] [event1] [structure2] [structure3] [structure4] [structure5] [structure6] [event2] [structure7]", "answers"=>[
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>4390, "blank_id"=>"structure1"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>1522, "blank_id"=>"structure1"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>7446, "blank_id"=>"structure1"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>279, "blank_id"=>"structure1"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>3390, "blank_id"=>"event1"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>5498, "blank_id"=>"event1"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>6955, "blank_id"=>"structure2"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>1228, "blank_id"=>"structure2"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>6578, "blank_id"=>"structure2"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>7676, "blank_id"=>"structure3"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>5213, "blank_id"=>"structure3"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>5988, "blank_id"=>"structure3"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>7604, "blank_id"=>"structure4"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>9532, "blank_id"=>"structure4"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>4772, "blank_id"=>"structure4"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>3353, "blank_id"=>"event2"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>599, "blank_id"=>"event2"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>9908, "blank_id"=>"structure5"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>878, "blank_id"=>"structure5"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>670, "blank_id"=>"structure5"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>4351, "blank_id"=>"structure5"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>6994, "blank_id"=>"structure6"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>1883, "blank_id"=>"structure6"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>4717, "blank_id"=>"structure6"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>7697, "blank_id"=>"structure6"},
{"comments"=>"", "weight"=>100, "text"=>"y", "id"=>1121, "blank_id"=>"structure7"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>9570, "blank_id"=>"structure7"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>4764, "blank_id"=>"structure7"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>3477, "blank_id"=>"structure7"},
{"comments"=>"", "weight"=>0, "text"=>"n", "id"=>461, "blank_id"=>"structure7"}
], "question_text"=>"[structure1] [event1] [structure2] [structure3] [structure4] [structure5] [structure6] [event2] [structure7]"}.with_indifferent_access
end
# @param [Hash] options
# @param [Boolean] options.answer_parser_compatibility
# Set this to true if you want the fixture to be compatible with
# QuizQuestion::AnswerParsers::Matching.
def matching_question_data(options = {})
data = {"id" => 1, "name"=>"Question", "correct_comments"=>"", "question_type"=>"matching_question", "assessment_question_id"=>4, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>50.0, "matches"=>[{"match_id"=>6061, "text"=>"1"}, {"match_id"=>3855, "text"=>"2"}, {"match_id"=>1397, "text"=>"1"}, {"match_id"=>2369, "text"=>"3"}, {"match_id"=>6065, "text"=>"4"}, {"match_id"=>5779, "text"=>"5"}, {"match_id"=>3562, "text"=>"6"}, {"match_id"=>1500, "text"=>"7"}, {"match_id"=>8513, "text"=>"8"}, {"match_id" => 6067, "text" => "a2"}, {"match_id" => 6068, "text" => "a3"}, {"match_id" => 6069, "text" => "a4"}], "answers"=>[
{"left"=>"a", "comments"=>"", "match_id"=>6061, "text"=>"a", "id"=>7396, "right"=>"1"},
{"left"=>"b", "comments"=>"", "match_id"=>3855, "text"=>"b", "id"=>6081, "right"=>"2"},
{"left"=>"ca", "comments"=>"", "match_id"=>1397, "text"=>"ca", "id"=>4224, "right"=>"1"},
{"left"=>"a2", "comments"=>"", "match_id"=>6067, "text"=>"a", "id"=>7397, "right"=>"a2"},
{"left"=>"a3", "comments"=>"", "match_id"=>6068, "text"=>"a", "id"=>7398, "right"=>"a3"},
{"left"=>"a4", "comments"=>"", "match_id"=>6069, "text"=>"a", "id"=>7399, "right"=>"a4"},
], "question_text"=>"<p>Test Question</p>"}.with_indifferent_access
if options[:answer_parser_compatibility]
data['answers'].each do |record|
record['answer_match_left'] = record['left']
record['answer_match_text'] = record['text']
record['answer_match_right'] = record['right']
record['answer_comments'] = record['comments']
%w[ left text right comments ].each { |k| record.delete k }
end
# match#1397 has a duplicate text with #7396 that needs to be adjusted
i = data['matches'].index { |record| record['match_id'] == 1397 }
data['matches'][i]['text'] = '_1'
i = data['answers'].index { |record| record['match_id'] == 1397 }
data['answers'][i]['text'] = '_1'
end
data
end
def numerical_question_data
{"name"=>"Question", "correct_comments"=>"", "question_type"=>"numerical_question", "assessment_question_id"=>8197062, "neutral_comments"=>"", "incorrect_comments"=>"", "question_name"=>"Question", "points_possible"=>26.2, "answers"=>[
{"exact"=>4, "comments"=>"", "numerical_answer_type"=>"exact_answer", "margin"=>0, "weight"=>100, "text"=>"", "id"=>9222},
{"exact"=>-4, "comments"=>"", "numerical_answer_type"=>"exact_answer", "margin"=>0, "weight"=>100, "text"=>"", "id"=>997},
{"comments"=>"", "numerical_answer_type"=>"range_answer", "weight"=>100, "text"=>"", "id"=>9370, "end"=>4.1, "start"=>3.9},
{"exact"=>-4, "comments"=>"", "numerical_answer_type"=>"exact_answer", "margin"=>0.1, "weight"=>100, "text"=>"", "id"=>5450},
{"numerical_answer_type"=>"precision_answer", "approximate"=>"1.23456e+21", "precision"=>"6", "comments"=>"", "weight"=>100, "text"=>"", "id"=>123}
], "question_text"=>"<p>abs(x) = 4</p>", "id" => 1}.with_indifferent_access
end
def calculated_question_data
{"name"=>"Question",
"correct_comments"=>"",
"answer_tolerance"=>0.02,
"question_type"=>"calculated_question",
"formulas"=>[{"formula"=>"5 + (x - y)"}],
"assessment_question_id"=>8197062,
"variables"=>
[{"name"=>"x", "scale"=>2, "max"=>7, "min"=>2},
{"name"=>"y", "scale"=>0, "max"=>23, "min"=>17}],
"neutral_comments"=>"",
"incorrect_comments"=>"",
"question_name"=>"Question",
"points_possible"=>26.2,
"formula_decimal_places"=>2,
"answers"=> [
{"variables"=>[{"name"=>"x", "value"=>4.3}, {"name"=>"y", "value"=>21}],
"weight"=>100,
"id"=>6396,
"answer"=>-11.7},
],
"question_text"=>"<p>What is 5 plus [x] - [y]</p>",
"id" => 1}.with_indifferent_access
end
# @param [Hash] options
# @param [Boolean] options.answer_parser_compatibility
# Set this to true if you want the fixture to be compatible with
# QuizQuestion::AnswerParsers::MultipleAnswers.
def multiple_answers_question_data(options = {})
data = {"name"=>"Question",
"correct_comments"=>"",
"question_type"=>"multiple_answers_question",
"assessment_question_id"=>8197062,
"neutral_comments"=>"",
"incorrect_comments"=>"",
"question_name"=>"Question",
"points_possible"=>50,
"answers"=>
[{"comments"=>"", "weight"=>100, "text"=>"5", "id"=>9761},
{"comments"=>"", "weight"=>0, "text"=>"cat", "id"=>3079},
{"comments"=>"", "weight"=>100, "text"=>"8", "id"=>5194},
{"comments"=>"", "weight"=>100, "text"=>"37.5", "id"=>166},
{"comments"=>"", "weight"=>0, "text"=>"airplane", "id"=>4739},
{"comments"=>"", "weight"=>100, "text"=>"114", "id"=>2196},
{"comments"=>"", "weight"=>100, "text"=>"869", "id"=>8982},
{"comments"=>"", "weight"=>100, "text"=>"431", "id"=>9701},
{"comments"=>"", "weight"=>0, "text"=>"schadenfreude", "id"=>7381}],
"question_text"=>"<p>which of these are numbers?</p>", "id" => 1}.with_indifferent_access
if options[:answer_parser_compatibility]
data['answers'].each do |record|
record['answer_weight'] = record['weight']
end
end
data
end
def fill_in_multiple_blanks_question_data
{:position=>1, :name=>"Question 1", :correct_comments=>"", :question_type=>"fill_in_multiple_blanks_question", :assessment_question_id=>7903, :incorrect_comments=>"", :neutral_comments=>"", :id=>1, :points_possible=>50, :question_name=>"Question 1", :answers=>[
{:comments=>"", :text=>"control", :weight=>100, :id=>3950, :blank_id=>"answer1"},
{:comments=>"", :text=>"controll", :weight=>100, :id=>9177, :blank_id=>"answer1"},
{:comments=>"", :text=>"patrol", :weight=>100, :id=>9181, :blank_id=>"answer2"},
{:comments=>"", :text=>"soul", :weight=>100, :id=>3733, :blank_id=>"answer3"},
{:comments=>"", :text=>"tolls", :weight=>100, :id=>9756, :blank_id=>"answer4"},
{:comments=>"", :text=>"toll", :weight=>100, :id=>7829, :blank_id=>"answer4"},
{:comments=>"", :text=>"explode", :weight=>100, :id=>3046, :blank_id=>"answer5"},
{:comments=>"", :text=>"assplode", :weight=>100, :id=>5301, :blank_id=>"answer5"},
{:comments=>"", :text=>"old", :weight=>100, :id=>3367, :blank_id=>"answer6"}
], :question_text=>"<p><span>Ayo my quality [answer1], captivates your party [answer2]. </span>Your mind, body, and [answer3]. For whom the bell [answer4], let the rhythm [answer5]. Big, bad, and bold b-boys of [answer6].</p>"}.with_indifferent_access
end
def fill_in_multiple_blanks_question_one_blank_data
{ :name => "Question", :question_type => "fill_in_multiple_blanks_question", :id => 2, :points_possible => 3.75, :answers => [
{ :text => "stupid", :weight => 100, :id => 1234, :blank_id => "myblank" },
{ :text => "dumb", :weight => 100, :id => 1235, :blank_id => "myblank" },
], :question_text => "<p>there's no such thing as a [myblank] question</p>" }.with_indifferent_access
end
def assignment_quiz(questions, opts={})
course = opts[:course] || course_factory(active_course: true)
user = opts[:user] || user_factory(:active_user => true)
course.enroll_student(user, :enrollment_state => 'active') unless user.enrollments.any? { |e| e.course_id == course.id }
@assignment = course.assignments.create(title: opts.fetch(:title, "Test Assignment"))
@assignment.workflow_state = "published"
@assignment.submission_types = "online_quiz"
@assignment.save
@quiz = Quizzes::Quiz.where(assignment_id: @assignment).first
@questions = questions.map { |q| @quiz.quiz_questions.create!(q.slice(:quiz_group, :assessment_question, :question_data, :assessment_question_version)) }
@quiz.generate_quiz_data
@quiz.due_at = opts.fetch(:due_at, Time.zone.now.advance(days: 7))
@quiz.published_at = Time.zone.now
@quiz.workflow_state = "available"
@quiz.save!
@quiz
end
# The block should return the submission_data. We pass the newly created
# questions to the block (legacy code uses @questions)
def quiz_with_graded_submission(questions, opts={}, &block)
assignment_quiz(questions, opts)
@quiz_submission = graded_submission(@quiz, @user, &block)
end
def graded_submission(quiz, user)
quiz_submission = quiz.generate_submission(user)
quiz_submission.mark_completed
quiz_submission.submission_data = yield(quiz.quiz_questions.order(:id)) if block_given?
Quizzes::SubmissionGrader.new(quiz_submission).grade_submission
quiz_submission
end
def survey_with_submission(questions, &block)
course_with_student(:active_all => true)
@assignment = @course.assignments.create(:title => "Test Assignment")
@assignment.workflow_state = "published"
@assignment.submission_types = "online_quiz"
@assignment.save
@quiz = Quizzes::Quiz.where(assignment_id: @assignment).first
@quiz.anonymous_submissions = true
@quiz.quiz_type = "graded_survey"
@questions = questions.map { |q| @quiz.quiz_questions.create!(q) }
@quiz.generate_quiz_data
@quiz.save!
@quiz_submission = @quiz.generate_submission(@user)
@quiz_submission.mark_completed
@quiz_submission.submission_data = yield if block_given?
end
def course_quiz(active=false)
@quiz = @course.quizzes.create
@quiz.workflow_state = "available" if active
@quiz.save!
@quiz
end
end