vdd backend for quizzes
[Fixes #CNVS-1145] * Adds support for attaching AssignmentOverrides to quizzes * Links common overrides for Assignments that belong to Quizzes so quiz.due_dates_for(user) == quiz.assignment.due_dates_for(user) * Moves common functionality for VDD out of the Assignment model and into the DatesOverridable module, which is now included on Quiz for a consistent interface Test plan: * Run through a few of the old VDD scenarios and verify things still work as expected since a lot of code has shifted around * No interface changes have been made to quizzes yet, so nothing to check there https://gist.github.com/f12b3694016f3dcc979e Change-Id: I32b4a54273f90fac689e05174039b8efc952dd39 Reviewed-on: https://gerrit.instructure.com/16028 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Simon Williams <simon@instructure.com> QA-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
parent
61259ca30d
commit
3b3b746cca
|
@ -25,6 +25,7 @@ class Assignment < ActiveRecord::Base
|
|||
include CopyAuthorizedLinks
|
||||
include Mutable
|
||||
include ContextModuleItem
|
||||
include DatesOverridable
|
||||
|
||||
attr_accessible :title, :name, :description, :due_at, :points_possible,
|
||||
:min_score, :max_score, :mastery_score, :grading_type, :submission_types,
|
||||
|
@ -34,7 +35,7 @@ class Assignment < ActiveRecord::Base
|
|||
:notify_of_update, :time_zone_edited, :turnitin_enabled, :turnitin_settings,
|
||||
:set_custom_field_values, :context, :position, :allowed_extensions,
|
||||
:external_tool_tag_attributes, :freeze_on_copy
|
||||
attr_accessor :original_id, :updating_user, :copying, :applied_overrides
|
||||
attr_accessor :original_id, :updating_user, :copying
|
||||
|
||||
has_many :submissions, :class_name => 'Submission', :dependent => :destroy
|
||||
has_many :attachments, :as => :context, :dependent => :destroy
|
||||
|
@ -49,12 +50,10 @@ class Assignment < ActiveRecord::Base
|
|||
belongs_to :cloned_item
|
||||
belongs_to :grading_standard
|
||||
belongs_to :group_category
|
||||
has_many :assignment_overrides, :dependent => :destroy
|
||||
has_many :active_assignment_overrides, :class_name => 'AssignmentOverride', :conditions => {:workflow_state => 'active'}
|
||||
|
||||
has_one :external_tool_tag, :class_name => 'ContentTag', :as => :context, :dependent => :destroy
|
||||
validates_associated :external_tool_tag, :if => :external_tool?
|
||||
validates_associated :assignment_overrides
|
||||
|
||||
accepts_nested_attributes_for :external_tool_tag, :reject_if => proc { |attrs|
|
||||
# only accept the url and new_tab params, the other accessible
|
||||
# params don't apply to an content tag being used as an external_tool_tag
|
||||
|
@ -143,99 +142,10 @@ class Assignment < ActiveRecord::Base
|
|||
if group_category_id_changed?
|
||||
# needs to be .each(&:destroy) instead of .update_all(:workflow_state =>
|
||||
# 'deleted') so that the override gets versioned properly
|
||||
assignment_overrides.active.scoped(:conditions => {:set_type => 'Group'}).each(&:destroy)
|
||||
active_assignment_overrides.scoped(:conditions => {:set_type => 'Group'}).each(&:destroy)
|
||||
end
|
||||
end
|
||||
|
||||
def overridden_for(user)
|
||||
AssignmentOverrideApplicator.assignment_overridden_for(self, user)
|
||||
end
|
||||
|
||||
def overrides_visible_to(user, overrides=active_assignment_overrides)
|
||||
# the visible_to scope is potentially expensive. skip its conditions if the
|
||||
# initial scope is already empty
|
||||
if overrides.first.present?
|
||||
overrides.visible_to(user, context)
|
||||
else
|
||||
overrides
|
||||
end
|
||||
end
|
||||
|
||||
def has_overrides?
|
||||
assignment_overrides.count > 0
|
||||
end
|
||||
|
||||
# returns two values indicating which due dates for this assignment apply
|
||||
# and/or are visible to the user.
|
||||
#
|
||||
# the first is the due date as it applies to the user as a student, if any
|
||||
# (nil if the user has no student enrollment(s) in the assignment's course)
|
||||
#
|
||||
# the second is a list of due dates a they apply to users, sections, or
|
||||
# groups visible to the user as an admin (nil if the user has no
|
||||
# admin/observer enrollment(s) in the assignment's course)
|
||||
#
|
||||
# in both cases, "due dates" is a hash with due_at (full timestamp), all_day
|
||||
# flag, and all_day_date. for the "as an admin" list, each due date from
|
||||
# an override will also have a 'title' key to identify which subset of the
|
||||
# course is affected by that due date, and an 'override' key referencing the
|
||||
# override itself. for the original due date, it will instead have a 'base'
|
||||
# flag (value true).
|
||||
def due_dates_for(user)
|
||||
as_student, as_admin = nil, nil
|
||||
return nil, nil if context.nil?
|
||||
|
||||
if context.user_has_been_student?(user)
|
||||
as_student = self.overridden_for(user).due_date_hash
|
||||
end
|
||||
|
||||
if context.user_has_been_admin?(user)
|
||||
as_admin = due_dates_visible_to(user)
|
||||
|
||||
elsif context.user_has_been_observer?(user)
|
||||
as_admin = observed_student_due_dates(user).uniq
|
||||
|
||||
if as_admin.empty?
|
||||
as_admin = [self.overridden_for(user).due_date_hash]
|
||||
end
|
||||
|
||||
elsif context.user_has_no_enrollments?(user)
|
||||
as_admin = all_due_dates
|
||||
end
|
||||
|
||||
return as_student, as_admin
|
||||
end
|
||||
|
||||
def all_due_dates
|
||||
all_dates = assignment_overrides.overriding_due_at.map(&:as_hash)
|
||||
all_dates << due_date_hash.merge(:base => true)
|
||||
end
|
||||
|
||||
def due_dates_visible_to(user)
|
||||
# Overrides
|
||||
overrides = overrides_visible_to(user).overriding_due_at
|
||||
list = overrides.map(&:as_hash)
|
||||
|
||||
# Base
|
||||
list << self.due_date_hash.merge(:base => true)
|
||||
end
|
||||
|
||||
def observed_student_due_dates(user)
|
||||
ObserverEnrollment.observed_students(context, user).map do |student, enrollments|
|
||||
self.overridden_for(student).due_date_hash
|
||||
end
|
||||
end
|
||||
|
||||
def due_date_hash
|
||||
hash = { :due_at => due_at, :all_day => all_day, :all_day_date => all_day_date }
|
||||
if @applied_overrides && override = @applied_overrides.find { |o| o.due_at == due_at }
|
||||
hash[:override] = override
|
||||
hash[:title] = override.title
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
def self.due_date_compare_value(date)
|
||||
# due dates are considered equal if they're the same up to the minute
|
||||
date.to_i / 60
|
||||
|
@ -245,72 +155,6 @@ class Assignment < ActiveRecord::Base
|
|||
due_date_compare_value(date1) == due_date_compare_value(date2)
|
||||
end
|
||||
|
||||
def multiple_due_dates_apply_to(user)
|
||||
as_instructor = self.due_dates_for(user).second
|
||||
as_instructor && as_instructor.map{ |hash|
|
||||
Assignment.due_date_compare_value(hash[:due_at]) }.uniq.size > 1
|
||||
end
|
||||
|
||||
# like due_dates_for, but for unlock_at values instead. for consistency, each
|
||||
# unlock_at is still represented by a hash, even though the "as a student"
|
||||
# value will only have one key.
|
||||
def unlock_ats_for(user)
|
||||
as_student, as_instructor = nil, nil
|
||||
|
||||
if context.user_has_been_student?(user)
|
||||
overridden = self.overridden_for(user)
|
||||
as_student = { :unlock_at => overridden.unlock_at }
|
||||
end
|
||||
|
||||
if context.user_has_been_instructor?(user)
|
||||
overrides = self.overrides_visible_to(user).overriding_unlock_at
|
||||
|
||||
as_instructor = overrides.map do |override|
|
||||
{
|
||||
:title => override.title,
|
||||
:unlock_at => override.unlock_at,
|
||||
:override => override
|
||||
}
|
||||
end
|
||||
|
||||
as_instructor << {
|
||||
:base => true,
|
||||
:unlock_at => self.unlock_at
|
||||
}
|
||||
end
|
||||
|
||||
return as_student, as_instructor
|
||||
end
|
||||
|
||||
# like unlock_ats_for, but for lock_at values instead.
|
||||
def lock_ats_for(user)
|
||||
as_student, as_instructor = nil, nil
|
||||
|
||||
if context.user_has_been_student?(user)
|
||||
overridden = self.overridden_for(user)
|
||||
as_student = { :lock_at => overridden.lock_at }
|
||||
end
|
||||
|
||||
if context.user_has_been_instructor?(user)
|
||||
overrides = self.overrides_visible_to(user).overriding_lock_at
|
||||
|
||||
as_instructor = overrides.map do |override|
|
||||
{
|
||||
:title => override.title,
|
||||
:lock_at => override.lock_at,
|
||||
:override => override
|
||||
}
|
||||
end
|
||||
|
||||
as_instructor << {
|
||||
:base => true,
|
||||
:lock_at => self.lock_at
|
||||
}
|
||||
end
|
||||
|
||||
return as_student, as_instructor
|
||||
end
|
||||
|
||||
def schedule_do_auto_peer_review_job_if_automatic_peer_review
|
||||
if peer_reviews && automatic_peer_reviews && !peer_reviews_assigned
|
||||
# handle if it has already come due, but has not yet been auto_peer_reviewed
|
||||
|
|
|
@ -25,10 +25,12 @@ class AssignmentOverride < ActiveRecord::Base
|
|||
attr_accessible
|
||||
|
||||
belongs_to :assignment
|
||||
belongs_to :quiz
|
||||
belongs_to :set, :polymorphic => true
|
||||
has_many :assignment_override_students, :dependent => :destroy
|
||||
|
||||
validates_presence_of :assignment, :assignment_version, :title
|
||||
validates_presence_of :assignment_version, :if => :assignment
|
||||
validates_presence_of :title
|
||||
validates_inclusion_of :set_type, :in => %w(CourseSection Group ADHOC)
|
||||
validates_length_of :title, :maximum => maximum_string_length, :allow_nil => true
|
||||
|
||||
|
@ -53,6 +55,12 @@ class AssignmentOverride < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
validate do |record|
|
||||
if [record.assignment, record.quiz].all?(&:nil?)
|
||||
record.errors.add :base, "assignment or quiz required"
|
||||
end
|
||||
end
|
||||
|
||||
workflow do
|
||||
state :active
|
||||
state :deleted
|
||||
|
@ -72,7 +80,17 @@ class AssignmentOverride < ActiveRecord::Base
|
|||
before_validation :default_values
|
||||
def default_values
|
||||
self.set_type ||= 'ADHOC'
|
||||
self.assignment_version = assignment.version_number if assignment
|
||||
|
||||
if assignment
|
||||
self.assignment_version = assignment.version_number
|
||||
self.quiz = assignment.quiz
|
||||
self.quiz_version = quiz.version_number if quiz
|
||||
elsif quiz
|
||||
self.quiz_version = quiz.version_number
|
||||
self.assignment = quiz.assignment
|
||||
self.assignment_version = assignment.version_number if assignment
|
||||
end
|
||||
|
||||
self.title = set.name if set_type != 'ADHOC' && set
|
||||
end
|
||||
protected :default_values
|
||||
|
|
|
@ -20,11 +20,12 @@ class AssignmentOverrideStudent < ActiveRecord::Base
|
|||
belongs_to :assignment
|
||||
belongs_to :assignment_override
|
||||
belongs_to :user
|
||||
belongs_to :quiz
|
||||
|
||||
attr_accessible :user
|
||||
|
||||
validates_presence_of :assignment, :assignment_override, :user
|
||||
validates_uniqueness_of :user_id, :scope => :assignment_id
|
||||
validates_presence_of :assignment_override, :user
|
||||
validates_uniqueness_of :user_id, :scope => [:assignment_id, :quiz_id]
|
||||
|
||||
validate :assignment_override do |record|
|
||||
if record.assignment_override && record.assignment_override.set_type != 'ADHOC'
|
||||
|
@ -39,14 +40,31 @@ class AssignmentOverrideStudent < ActiveRecord::Base
|
|||
end
|
||||
|
||||
validate :user do |record|
|
||||
if record.user && record.assignment && record.user.student_enrollments.scoped(:conditions => {:course_id => record.assignment.context_id}).first.nil?
|
||||
if record.user && record.context_id && record.user.student_enrollments.scoped(:conditions => {:course_id => record.context_id}).first.nil?
|
||||
record.errors.add :user, "is not in the assignment's course"
|
||||
end
|
||||
end
|
||||
|
||||
validate do |record|
|
||||
if [record.assignment, record.quiz].all?(&:nil?)
|
||||
record.errors.add :base, "requires assignment or quiz"
|
||||
end
|
||||
end
|
||||
|
||||
def context_id
|
||||
if quiz
|
||||
quiz.context_id
|
||||
elsif assignment
|
||||
assignment.context_id
|
||||
end
|
||||
end
|
||||
|
||||
before_validation :default_values
|
||||
def default_values
|
||||
self.assignment_id = self.assignment_override.assignment_id if self.assignment_override
|
||||
if assignment_override
|
||||
self.assignment_id = assignment_override.assignment_id
|
||||
self.quiz_id = assignment_override.quiz_id
|
||||
end
|
||||
end
|
||||
protected :default_values
|
||||
end
|
||||
|
|
|
@ -25,6 +25,7 @@ class Quiz < ActiveRecord::Base
|
|||
include ActionView::Helpers::SanitizeHelper
|
||||
extend ActionView::Helpers::SanitizeHelper::ClassMethods
|
||||
include ContextModuleItem
|
||||
include DatesOverridable
|
||||
|
||||
attr_accessible :title, :description, :points_possible, :assignment_id, :shuffle_answers,
|
||||
:show_correct_answers, :time_limit, :allowed_attempts, :scoring_policy, :quiz_type,
|
||||
|
@ -53,6 +54,7 @@ class Quiz < ActiveRecord::Base
|
|||
before_save :set_defaults
|
||||
after_save :update_assignment
|
||||
after_save :touch_context
|
||||
after_save :link_assignment_overrides, :if => :new_assignment_id?
|
||||
|
||||
serialize :quiz_data
|
||||
|
||||
|
@ -89,6 +91,19 @@ class Quiz < ActiveRecord::Base
|
|||
@stored_questions = nil
|
||||
end
|
||||
protected :set_defaults
|
||||
|
||||
def new_assignment_id?
|
||||
last_assignment_id != assignment_id
|
||||
end
|
||||
|
||||
def link_assignment_overrides
|
||||
collections = [assignment_overrides, assignment_override_students]
|
||||
collections += [assignment.assignment_overrides, assignment.assignment_override_students] if assignment
|
||||
|
||||
collections.each do |collection|
|
||||
collection.update_all({ :assignment_id => assignment_id, :quiz_id => id })
|
||||
end
|
||||
end
|
||||
|
||||
def build_assignment
|
||||
if self.available? && !self.assignment_id && self.graded? && @saved_by != :assignment && @saved_by != :clone
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
class AddQuizIdToAssignmentOverrides < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
add_column :assignment_overrides, :quiz_id, :integer, :limit => 8
|
||||
add_column :assignment_overrides, :quiz_version, :integer
|
||||
add_index :assignment_overrides, :quiz_id
|
||||
|
||||
change_column :assignment_overrides, :assignment_id, :integer, :limit => 8, :null => true
|
||||
change_column :assignment_overrides, :assignment_version, :integer, :null => true
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :assignment_overrides, :quiz_id
|
||||
remove_column :assignment_overrides, :quiz_id, :quiz_version
|
||||
|
||||
change_column :assignment_overrides, :assignment_id, :integer, :limit => 8, :null => false
|
||||
change_column :assignment_overrides, :assignment_version, :integer, :null => false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
class AddQuizIdToAssignmentOverrideStudents < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
add_column :assignment_override_students, :quiz_id, :integer, :limit => 8
|
||||
add_index :assignment_override_students, :quiz_id
|
||||
change_column :assignment_override_students, :assignment_id, :integer, :limit => 8, :null => true
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :assignment_override_students, :quiz_id
|
||||
remove_index :assignment_override_students, :quiz_id
|
||||
change_column :assignment_override_students, :assignment_id, :integer, :limit => 8, :null => false
|
||||
end
|
||||
end
|
|
@ -17,39 +17,45 @@
|
|||
#
|
||||
|
||||
module AssignmentOverrideApplicator
|
||||
# top-level method intended for consumption. given an assignment (of specific
|
||||
# top-level method intended for consumption. given an assignment or quiz(of specific
|
||||
# version) and user, determine the list of overrides, apply them to the
|
||||
# assignment, and return the overridden stand-in.
|
||||
def self.assignment_overridden_for(assignment, user)
|
||||
overrides = self.overrides_for_assignment_and_user(assignment, user)
|
||||
# assignment or quiz, and return the overridden stand-in.
|
||||
def self.assignment_overridden_for(assignment_or_quiz, user)
|
||||
overrides = self.overrides_for_assignment_and_user(assignment_or_quiz, user)
|
||||
|
||||
if overrides.empty?
|
||||
assignment
|
||||
assignment_or_quiz
|
||||
else
|
||||
self.assignment_with_overrides(assignment, overrides)
|
||||
self.assignment_with_overrides(assignment_or_quiz, overrides)
|
||||
end
|
||||
end
|
||||
|
||||
def self.quiz_overridden_for(quiz, user)
|
||||
assignment_overridden_for(quiz, user)
|
||||
end
|
||||
|
||||
# determine list of overrides (of appropriate version) that apply to the
|
||||
# assignment (of specific version) for a particular user. the overrides are
|
||||
# assignment or quiz(of specific version) for a particular user. the overrides are
|
||||
# returned in priority order; the first override to contain an overridden
|
||||
# value for a particular field is used for that field
|
||||
def self.overrides_for_assignment_and_user(assignment, user)
|
||||
Rails.cache.fetch([user, assignment, assignment.version_number, 'overrides'].cache_key) do
|
||||
def self.overrides_for_assignment_and_user(assignment_or_quiz, user)
|
||||
Rails.cache.fetch([user, assignment_or_quiz, assignment_or_quiz.version_number, 'overrides'].cache_key) do
|
||||
|
||||
# return an empty array to the block if there is nothing to do here
|
||||
next [] unless assignment.has_overrides?
|
||||
next [] unless assignment_or_quiz.has_overrides?
|
||||
|
||||
overrides = []
|
||||
|
||||
# get list of overrides that might apply. adhoc override is highest
|
||||
# priority, then group override, then section overrides by position. DO
|
||||
# NOT exclude deleted overrides, yet
|
||||
adhoc_membership = AssignmentOverrideStudent.scoped(:conditions => {:assignment_id => assignment.id, :user_id => user.id}).first
|
||||
key = assignment_or_quiz.is_a?(Quiz) ? :quiz_id : :assignment_id
|
||||
adhoc_membership = AssignmentOverrideStudent.scoped(:conditions => {key => assignment_or_quiz.id, :user_id => user.id}).first
|
||||
|
||||
overrides << adhoc_membership.assignment_override if adhoc_membership
|
||||
|
||||
if assignment.group_category && group = user.current_groups.scoped(:conditions => {:group_category_id => assignment.group_category_id}).first
|
||||
group_override = assignment.assignment_overrides.
|
||||
if assignment_or_quiz.is_a?(Assignment) && assignment_or_quiz.group_category && group = user.current_groups.scoped(:conditions => {:group_category_id => assignment_or_quiz.group_category_id}).first
|
||||
group_override = assignment_or_quiz.assignment_overrides.
|
||||
scoped(:conditions => {:set_type => 'Group', :set_id => group.id}).
|
||||
first
|
||||
overrides << group_override if group_override
|
||||
|
@ -57,20 +63,21 @@ module AssignmentOverrideApplicator
|
|||
|
||||
section_ids = user.enrollments.active.scoped(:conditions =>
|
||||
{ :type => ['StudentEnrollment', 'ObserverEnrollment'],
|
||||
:course_id => assignment.context_id}).map(&:course_section_id)
|
||||
:course_id => assignment_or_quiz.context_id}).map(&:course_section_id)
|
||||
|
||||
section_overrides = assignment.assignment_overrides.
|
||||
section_overrides = assignment_or_quiz.assignment_overrides.
|
||||
scoped(:conditions => {:set_type => 'CourseSection', :set_id => section_ids})
|
||||
|
||||
# TODO add position column to assignment_override, nil for non-section
|
||||
# overrides, (assignment, position) unique for section overrides
|
||||
# overrides, (assignment_or_quiz, position) unique for section overrides
|
||||
overrides += section_overrides#.scoped(:order => :position)
|
||||
|
||||
# for each potential override discovered, make sure we look at the
|
||||
# appropriate version
|
||||
overrides = overrides.map do |override|
|
||||
override_version = override.versions.detect do |version|
|
||||
version.model.assignment_version <= assignment.version_number
|
||||
model_version = assignment_or_quiz.is_a?(Quiz) ? version.model.quiz_version : version.model.assignment_version
|
||||
model_version <= assignment_or_quiz.version_number
|
||||
end
|
||||
override_version ? override_version.model : nil
|
||||
end
|
||||
|
@ -82,47 +89,53 @@ module AssignmentOverrideApplicator
|
|||
end
|
||||
|
||||
# apply the overrides calculated by collapsed_overrides to a clone of the
|
||||
# assignment which can then be used in place of the assignment. the clone is
|
||||
# marked readonly to prevent saving
|
||||
def self.assignment_with_overrides(assignment, overrides)
|
||||
# assignment or quiz which can then be used in place of the original object.
|
||||
# the clone is marked readonly to prevent saving
|
||||
def self.assignment_with_overrides(assignment_or_quiz, overrides)
|
||||
# ActiveRecord::Base#clone nils out the primary key; put it back
|
||||
cloned_assignment = assignment.clone
|
||||
cloned_assignment.id = assignment.id
|
||||
cloned_assignment_or_quiz = assignment_or_quiz.clone
|
||||
cloned_assignment_or_quiz.id = assignment_or_quiz.id
|
||||
|
||||
# update attributes with overrides
|
||||
self.collapsed_overrides(assignment, overrides).each do |field,value|
|
||||
self.collapsed_overrides(assignment_or_quiz, overrides).each do |field,value|
|
||||
# for any times in the value set, bring them back from raw UTC into the
|
||||
# current Time.zone before placing them in the assignment
|
||||
value = value.in_time_zone if value && value.respond_to?(:in_time_zone) && !value.is_a?(Date)
|
||||
cloned_assignment.write_attribute(field, value)
|
||||
cloned_assignment_or_quiz.write_attribute(field, value)
|
||||
end
|
||||
cloned_assignment.applied_overrides = overrides
|
||||
cloned_assignment.readonly!
|
||||
cloned_assignment_or_quiz.applied_overrides = overrides
|
||||
cloned_assignment_or_quiz.readonly!
|
||||
|
||||
# make new_record? match the original (typically always true on AR clones,
|
||||
# at least until saved, which we don't want to do)
|
||||
klass = class << cloned_assignment; self; end
|
||||
klass.send(:define_method, :new_record?) { assignment.new_record? }
|
||||
klass = class << cloned_assignment_or_quiz; self; end
|
||||
klass.send(:define_method, :new_record?) { assignment_or_quiz.new_record? }
|
||||
|
||||
cloned_assignment
|
||||
cloned_assignment_or_quiz
|
||||
end
|
||||
|
||||
# given an assignment (of specific version) and an ordered list of overrides
|
||||
def self.quiz_with_overrides(quiz, overrides)
|
||||
assignment_with_overrides(quiz, overrides)
|
||||
end
|
||||
|
||||
# given an assignment or quiz (of specific version) and an ordered list of overrides
|
||||
# (see overrides_for_assignment_and_user), return a hash of values for each
|
||||
# overrideable field. for caching, the same set of overrides should produce
|
||||
# the same collapsed assignment, regardless of the user that ended up at that
|
||||
# the same collapsed assignment or quiz, regardless of the user that ended up at that
|
||||
# set of overrides.
|
||||
def self.collapsed_overrides(assignment, overrides)
|
||||
Rails.cache.fetch([assignment, assignment.version_number, self.overrides_hash(overrides)].cache_key) do
|
||||
def self.collapsed_overrides(assignment_or_quiz, overrides)
|
||||
Rails.cache.fetch([assignment_or_quiz, assignment_or_quiz.version_number, self.overrides_hash(overrides)].cache_key) do
|
||||
overridden_data = {}
|
||||
# clone the assignment, apply overrides, and freeze
|
||||
# clone the assignment_or_quiz, apply overrides, and freeze
|
||||
[:due_at, :all_day, :all_day_date, :unlock_at, :lock_at].each do |field|
|
||||
value = self.send("overridden_#{field}", assignment, overrides)
|
||||
# force times to un-zoned UTC -- this will be a cached value and should
|
||||
# not care about the TZ of the user that cached it. the user's TZ will
|
||||
# be applied before it's returned.
|
||||
value = value.utc if value && value.respond_to?(:utc) && !value.is_a?(Date)
|
||||
overridden_data[field] = value
|
||||
if assignment_or_quiz.respond_to?(field)
|
||||
value = self.send("overridden_#{field}", assignment_or_quiz, overrides)
|
||||
# force times to un-zoned UTC -- this will be a cached value and should
|
||||
# not care about the TZ of the user that cached it. the user's TZ will
|
||||
# be applied before it's returned.
|
||||
value = value.utc if value && value.respond_to?(:utc) && !value.is_a?(Date)
|
||||
overridden_data[field] = value
|
||||
end
|
||||
end
|
||||
overridden_data
|
||||
end
|
||||
|
@ -135,24 +148,24 @@ module AssignmentOverrideApplicator
|
|||
end
|
||||
|
||||
# perform overrides of specific fields
|
||||
def self.override_for_due_at(assignment, overrides)
|
||||
def self.override_for_due_at(assignment_or_quiz, overrides)
|
||||
applicable_overrides = overrides.select(&:due_at_overridden)
|
||||
if applicable_overrides.empty?
|
||||
assignment
|
||||
assignment_or_quiz
|
||||
elsif override = applicable_overrides.detect{ |o| o.due_at.nil? }
|
||||
override
|
||||
else
|
||||
override = applicable_overrides.sort_by(&:due_at).last
|
||||
if assignment.due_at && assignment.due_at > override.due_at
|
||||
assignment
|
||||
if assignment_or_quiz.due_at && assignment_or_quiz.due_at > override.due_at
|
||||
assignment_or_quiz
|
||||
else
|
||||
override
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.overridden_due_at(assignment, overrides)
|
||||
override_for_due_at(assignment, overrides).due_at
|
||||
def self.overridden_due_at(assignment_or_quiz, overrides)
|
||||
override_for_due_at(assignment_or_quiz, overrides).due_at
|
||||
end
|
||||
|
||||
def self.overridden_all_day(assignment, overrides)
|
||||
|
@ -163,13 +176,13 @@ module AssignmentOverrideApplicator
|
|||
override_for_due_at(assignment, overrides).all_day_date
|
||||
end
|
||||
|
||||
def self.overridden_unlock_at(assignment, overrides)
|
||||
def self.overridden_unlock_at(assignment_or_quiz, overrides)
|
||||
unlock_ats = overrides.select(&:unlock_at_overridden).map(&:unlock_at)
|
||||
unlock_ats.any?(&:nil?) ? nil : [assignment.unlock_at, *unlock_ats].compact.min
|
||||
unlock_ats.any?(&:nil?) ? nil : [assignment_or_quiz.unlock_at, *unlock_ats].compact.min
|
||||
end
|
||||
|
||||
def self.overridden_lock_at(assignment, overrides)
|
||||
def self.overridden_lock_at(assignment_or_quiz, overrides)
|
||||
lock_ats = overrides.select(&:lock_at_overridden).map(&:lock_at)
|
||||
lock_ats.any?(&:nil?) ? nil : [assignment.lock_at, *lock_ats].compact.max
|
||||
lock_ats.any?(&:nil?) ? nil : [assignment_or_quiz.lock_at, *lock_ats].compact.max
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
module DatesOverridable
|
||||
attr_accessor :applied_overrides
|
||||
|
||||
def self.included(base)
|
||||
base.has_many :assignment_overrides, :dependent => :destroy
|
||||
base.has_many :active_assignment_overrides, :class_name => 'AssignmentOverride', :conditions => {:workflow_state => 'active'}
|
||||
base.has_many :assignment_override_students, :dependent => :destroy
|
||||
|
||||
base.validates_associated :assignment_overrides
|
||||
end
|
||||
|
||||
def overridden_for(user)
|
||||
AssignmentOverrideApplicator.assignment_overridden_for(self, user)
|
||||
end
|
||||
|
||||
def overrides_visible_to(user, overrides=active_assignment_overrides)
|
||||
# the visible_to scope is potentially expensive. skip its conditions if the
|
||||
# initial scope is already empty
|
||||
if overrides.first.present?
|
||||
overrides.visible_to(user, context)
|
||||
else
|
||||
overrides
|
||||
end
|
||||
end
|
||||
|
||||
def has_overrides?
|
||||
assignment_overrides.count > 0
|
||||
end
|
||||
|
||||
# returns two values indicating which due dates for this assignment apply
|
||||
# and/or are visible to the user.
|
||||
#
|
||||
# the first is the due date as it applies to the user as a student, if any
|
||||
# (nil if the user has no student enrollment(s) in the assignment's course)
|
||||
#
|
||||
# the second is a list of due dates a they apply to users, sections, or
|
||||
# groups visible to the user as an admin (nil if the user has no
|
||||
# admin/observer enrollment(s) in the assignment's course)
|
||||
#
|
||||
# in both cases, "due dates" is a hash with due_at (full timestamp), all_day
|
||||
# flag, and all_day_date. for the "as an admin" list, each due date from
|
||||
# an override will also have a 'title' key to identify which subset of the
|
||||
# course is affected by that due date, and an 'override' key referencing the
|
||||
# override itself. for the original due date, it will instead have a 'base'
|
||||
# flag (value true).
|
||||
def due_dates_for(user)
|
||||
as_student, as_admin = nil, nil
|
||||
return nil, nil if context.nil?
|
||||
|
||||
if context.user_has_been_student?(user)
|
||||
as_student = self.overridden_for(user).due_date_hash
|
||||
end
|
||||
|
||||
if context.user_has_been_admin?(user)
|
||||
as_admin = due_dates_visible_to(user)
|
||||
|
||||
elsif context.user_has_been_observer?(user)
|
||||
as_admin = observed_student_due_dates(user).uniq
|
||||
|
||||
if as_admin.empty?
|
||||
as_admin = [self.overridden_for(user).due_date_hash]
|
||||
end
|
||||
|
||||
elsif context.user_has_no_enrollments?(user)
|
||||
as_admin = all_due_dates
|
||||
end
|
||||
|
||||
return as_student, as_admin
|
||||
end
|
||||
|
||||
def all_due_dates
|
||||
all_dates = assignment_overrides.overriding_due_at.map(&:as_hash)
|
||||
all_dates << due_date_hash.merge(:base => true)
|
||||
end
|
||||
|
||||
def due_dates_visible_to(user)
|
||||
# Overrides
|
||||
overrides = overrides_visible_to(user).overriding_due_at
|
||||
list = overrides.map(&:as_hash)
|
||||
|
||||
# Base
|
||||
list << self.due_date_hash.merge(:base => true)
|
||||
end
|
||||
|
||||
def observed_student_due_dates(user)
|
||||
ObserverEnrollment.observed_students(context, user).map do |student, enrollments|
|
||||
self.overridden_for(student).due_date_hash
|
||||
end
|
||||
end
|
||||
|
||||
def due_date_hash
|
||||
hash = { :due_at => due_at }
|
||||
|
||||
if self.is_a?(Assignment)
|
||||
hash.merge!({ :all_day => all_day, :all_day_date => all_day_date })
|
||||
end
|
||||
|
||||
if @applied_overrides && override = @applied_overrides.find { |o| o.due_at == due_at }
|
||||
hash[:override] = override
|
||||
hash[:title] = override.title
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
def multiple_due_dates_apply_to(user)
|
||||
as_instructor = self.due_dates_for(user).second
|
||||
as_instructor && as_instructor.map{ |hash|
|
||||
self.class.due_date_compare_value(hash[:due_at]) }.uniq.size > 1
|
||||
end
|
||||
|
||||
# like due_dates_for, but for unlock_at values instead. for consistency, each
|
||||
# unlock_at is still represented by a hash, even though the "as a student"
|
||||
# value will only have one key.
|
||||
def unlock_ats_for(user)
|
||||
as_student, as_instructor = nil, nil
|
||||
|
||||
if context.user_has_been_student?(user)
|
||||
overridden = self.overridden_for(user)
|
||||
as_student = { :unlock_at => overridden.unlock_at }
|
||||
end
|
||||
|
||||
if context.user_has_been_instructor?(user)
|
||||
overrides = self.overrides_visible_to(user).overriding_unlock_at
|
||||
|
||||
as_instructor = overrides.map do |override|
|
||||
{
|
||||
:title => override.title,
|
||||
:unlock_at => override.unlock_at,
|
||||
:override => override
|
||||
}
|
||||
end
|
||||
|
||||
as_instructor << {
|
||||
:base => true,
|
||||
:unlock_at => self.unlock_at
|
||||
}
|
||||
end
|
||||
|
||||
return as_student, as_instructor
|
||||
end
|
||||
|
||||
# like unlock_ats_for, but for lock_at values instead.
|
||||
def lock_ats_for(user)
|
||||
as_student, as_instructor = nil, nil
|
||||
|
||||
if context.user_has_been_student?(user)
|
||||
overridden = self.overridden_for(user)
|
||||
as_student = { :lock_at => overridden.lock_at }
|
||||
end
|
||||
|
||||
if context.user_has_been_instructor?(user)
|
||||
overrides = self.overrides_visible_to(user).overriding_lock_at
|
||||
|
||||
as_instructor = overrides.map do |override|
|
||||
{
|
||||
:title => override.title,
|
||||
:lock_at => override.lock_at,
|
||||
:override => override
|
||||
}
|
||||
end
|
||||
|
||||
as_instructor << {
|
||||
:base => true,
|
||||
:lock_at => self.lock_at
|
||||
}
|
||||
end
|
||||
|
||||
return as_student, as_instructor
|
||||
end
|
||||
end
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
|
||||
def assignment_override_model(opts={})
|
||||
assignment = opts.delete(:assignment) || assignment_model(opts)
|
||||
assignment = opts.delete(:assignment) || opts.delete(:quiz) || assignment_model(opts)
|
||||
override_for = opts.delete(:set)
|
||||
@override = factory_with_protected_attributes(assignment.assignment_overrides, assignment_override_valid_attributes.merge(opts))
|
||||
@override.due_at_overridden = true if opts[:due_at]
|
||||
|
|
|
@ -0,0 +1,422 @@
|
|||
|
||||
#
|
||||
# Copyright (C) 2012 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/>.
|
||||
#
|
||||
|
||||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
||||
|
||||
shared_examples_for "an object whose dates are overridable" do
|
||||
# let(:overridable) - an Assignment or Quiz
|
||||
# let(:overridable_type) - :assignment or :quiz
|
||||
|
||||
let(:course) { overridable.context }
|
||||
let(:override) { assignment_override_model(overridable_type => overridable) }
|
||||
|
||||
describe "overridden_for" do
|
||||
before do
|
||||
student_in_course(:course => course)
|
||||
end
|
||||
|
||||
context "when there are overrides" do
|
||||
before do
|
||||
override.override_due_at(7.days.from_now)
|
||||
override.save!
|
||||
|
||||
override_student = override.assignment_override_students.build
|
||||
override_student.user = @student
|
||||
override_student.save!
|
||||
end
|
||||
|
||||
it "returns a clone of the object with the relevant override(s) applied" do
|
||||
overridden = overridable.overridden_for(@student)
|
||||
overridden.due_at.should == override.due_at
|
||||
end
|
||||
end
|
||||
|
||||
context "with no overrides" do
|
||||
it "returns the original object" do
|
||||
@overridden = overridable.overridden_for(@student)
|
||||
@overridden.due_at.should == overridable.due_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "has_overrides?" do
|
||||
subject { overridable.has_overrides? }
|
||||
|
||||
context "when it does" do
|
||||
before { override }
|
||||
it { should be_true }
|
||||
end
|
||||
|
||||
context "when it doesn't" do
|
||||
it { should be_false }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#overrides_visible_to(user)" do
|
||||
before :each do
|
||||
override.set = course.default_section
|
||||
override.save!
|
||||
end
|
||||
|
||||
it "delegates to visible_to on the active overrides by default" do
|
||||
@expected_value = stub("expected value")
|
||||
overridable.active_assignment_overrides.expects(:visible_to).with(@teacher, course).returns(@expected_value)
|
||||
overridable.overrides_visible_to(@teacher).should == @expected_value
|
||||
end
|
||||
|
||||
it "allows overriding the scope" do
|
||||
override.destroy
|
||||
overridable.overrides_visible_to(@teacher).should be_empty
|
||||
overridable.overrides_visible_to(@teacher, overridable.assignment_overrides(true)).should == [override]
|
||||
end
|
||||
|
||||
it "skips the visible_to application if the scope is already empty" do
|
||||
override.destroy
|
||||
overridable.active_assignment_overrides.expects(:visible_to).times(0)
|
||||
overridable.overrides_visible_to(@teacher)
|
||||
end
|
||||
|
||||
it "returns a scope" do
|
||||
# can't use "should respond_to", because that delegates to the instantiated Array
|
||||
lambda{ overridable.overrides_visible_to(@teacher).scoped({}) }.should_not raise_exception
|
||||
end
|
||||
end
|
||||
|
||||
describe "#due_dates_for(user)" do
|
||||
before :each do
|
||||
course_with_student(:course => course)
|
||||
|
||||
override.set = course.default_section
|
||||
override.override_due_at(2.days.ago)
|
||||
override.save!
|
||||
end
|
||||
|
||||
context "for a student" do
|
||||
before do
|
||||
@as_student, @as_instructor = overridable.due_dates_for(@student)
|
||||
end
|
||||
|
||||
it "does not return instructor dates" do
|
||||
@as_instructor.should be_nil
|
||||
end
|
||||
|
||||
it "returns a relevant student date" do
|
||||
@as_student.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "for a teacher" do
|
||||
before do
|
||||
@as_student, @as_instructor = overridable.due_dates_for(@teacher)
|
||||
end
|
||||
|
||||
it "does not return a student date" do
|
||||
@as_student.should be_nil
|
||||
end
|
||||
|
||||
it "returns a list of instructor dates" do
|
||||
@as_instructor.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "returns both for a user that's both a student and a teacher" do
|
||||
course_with_ta(:course => course, :user => @student, :active_all => true)
|
||||
as_student, as_instructor = overridable.due_dates_for(@student)
|
||||
as_student.should_not be_nil
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "uses the overridden due date as the applicable due date" do
|
||||
as_student, _ = overridable.due_dates_for(@student)
|
||||
as_student[:due_at].should == override.due_at
|
||||
|
||||
if overridable.is_a?(Assignment)
|
||||
as_student[:all_day].should == override.all_day
|
||||
as_student[:all_day_date].should == override.all_day_date
|
||||
end
|
||||
end
|
||||
|
||||
it "includes the base due date in the list of due dates" do
|
||||
_, as_instructor = overridable.due_dates_for(@teacher)
|
||||
|
||||
expected_params = { :base => true, :due_at => overridable.due_at }
|
||||
|
||||
if overridable.is_a?(Assignment)
|
||||
expected_params.merge!({
|
||||
:all_day => overridable.all_day,
|
||||
:all_day_date => overridable.all_day_date
|
||||
})
|
||||
end
|
||||
|
||||
as_instructor.should include expected_params
|
||||
end
|
||||
|
||||
it "includes visible due date overrides in the list of due dates" do
|
||||
_, as_instructor = overridable.due_dates_for(@teacher)
|
||||
as_instructor.should include({
|
||||
:title => @course.default_section.name,
|
||||
:due_at => override.due_at,
|
||||
:all_day => override.all_day,
|
||||
:all_day_date => override.all_day_date,
|
||||
:override => override
|
||||
})
|
||||
end
|
||||
|
||||
it "excludes visible overrides that don't override due_at from the list of due dates" do
|
||||
override.clear_due_at_override
|
||||
override.save!
|
||||
|
||||
_, as_instructor = overridable.due_dates_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
|
||||
it "excludes overrides that aren't visible from the list of due dates" do
|
||||
@enrollment = @teacher.enrollments.first
|
||||
@enrollment.limit_privileges_to_course_section = true
|
||||
@enrollment.save!
|
||||
|
||||
@section2 = course.course_sections.create!
|
||||
override.set = @section2
|
||||
override.save!
|
||||
|
||||
_, as_instructor = overridable.due_dates_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "due_date_hash" do
|
||||
it "returns the due at, all day, and all day date params" do
|
||||
due = 5.days.from_now
|
||||
a = Assignment.new(:due_at => due)
|
||||
a.due_date_hash.should == { :due_at => due, :all_day => false, :all_day_date => nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe "observed_student_due_dates" do
|
||||
it "returns a list of overridden due date hashes" do
|
||||
a = Assignment.new
|
||||
u = User.new
|
||||
student1, student2 = [mock, mock]
|
||||
|
||||
{ student1 => '1', student2 => '2' }.each do |student, value|
|
||||
a.expects(:overridden_for).with(student).returns \
|
||||
mock(:due_date_hash => { :student => value })
|
||||
end
|
||||
|
||||
ObserverEnrollment.expects(:observed_students).returns({student1 => [], student2 => []})
|
||||
|
||||
override_hashes = a.observed_student_due_dates(u)
|
||||
override_hashes.should =~ [ { :student => '1' }, { :student => '2' } ]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#unlock_ats_for(user)" do
|
||||
before :each do
|
||||
course_with_student(:course => course, :active_all => true)
|
||||
|
||||
overridable.update_attributes(:unlock_at => 2.days.ago)
|
||||
|
||||
override.set = course.default_section
|
||||
override.override_unlock_at(5.days.ago)
|
||||
override.save!
|
||||
end
|
||||
|
||||
context "for a student" do
|
||||
before do
|
||||
@as_student, @as_instructor = overridable.unlock_ats_for(@student)
|
||||
end
|
||||
|
||||
it "does not return instructor dates" do
|
||||
@as_instructor.should be_nil
|
||||
end
|
||||
|
||||
it "returns a relevant student date" do
|
||||
@as_student.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "for a teacher" do
|
||||
before do
|
||||
@as_student, @as_instructor = overridable.unlock_ats_for(@teacher)
|
||||
end
|
||||
|
||||
it "does not return a student date" do
|
||||
@as_student.should be_nil
|
||||
end
|
||||
|
||||
it "returns a list of instructor dates" do
|
||||
@as_instructor.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "returns both for a user that's both a student and a teacher" do
|
||||
course_with_ta(:course => course, :user => @student, :active_all => true)
|
||||
as_student, as_instructor = overridable.unlock_ats_for(@student)
|
||||
as_student.should_not be_nil
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "uses the overridden unlock date as the applicable unlock date" do
|
||||
as_student, _ = overridable.unlock_ats_for(@student)
|
||||
as_student.should == { :unlock_at => override.unlock_at }
|
||||
end
|
||||
|
||||
it "includes the base unlock date in the list of unlock dates" do
|
||||
_, as_instructor = overridable.unlock_ats_for(@teacher)
|
||||
as_instructor.should include({ :base => true, :unlock_at => overridable.unlock_at })
|
||||
end
|
||||
|
||||
it "includes visible unlock date overrides in the list of unlock dates" do
|
||||
_, as_instructor = overridable.unlock_ats_for(@teacher)
|
||||
as_instructor.should include({
|
||||
:title => @course.default_section.name,
|
||||
:unlock_at => override.unlock_at,
|
||||
:override => override
|
||||
})
|
||||
end
|
||||
|
||||
it "excludes visible overrides that don't override unlock_at from the list of unlock dates" do
|
||||
override.clear_unlock_at_override
|
||||
override.save!
|
||||
|
||||
_, as_instructor = overridable.unlock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
|
||||
it "excludes overrides that aren't visible from the list of unlock dates" do
|
||||
@enrollment = @teacher.enrollments.first
|
||||
@enrollment.limit_privileges_to_course_section = true
|
||||
@enrollment.save!
|
||||
|
||||
@section2 = @course.course_sections.create!
|
||||
override.set = @section2
|
||||
override.save!
|
||||
|
||||
_, as_instructor = overridable.unlock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#lock_ats_for(user)" do
|
||||
before :each do
|
||||
course_with_student(:course => course, :active_all => true)
|
||||
|
||||
overridable.update_attributes(:unlock_at => 5.days.ago)
|
||||
|
||||
override.set = course.default_section
|
||||
override.override_lock_at(2.days.ago)
|
||||
override.save!
|
||||
end
|
||||
|
||||
context "for a student" do
|
||||
before do
|
||||
@as_student, @as_instructor = overridable.lock_ats_for(@student)
|
||||
end
|
||||
|
||||
it "does not return instructor dates" do
|
||||
@as_instructor.should be_nil
|
||||
end
|
||||
|
||||
it "returns a relevant student date" do
|
||||
@as_student.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "for a teacher" do
|
||||
before do
|
||||
@as_student, @as_instructor = overridable.lock_ats_for(@teacher)
|
||||
end
|
||||
|
||||
it "does not return a student date" do
|
||||
@as_student.should be_nil
|
||||
end
|
||||
|
||||
it "returns a list of instructor dates" do
|
||||
@as_instructor.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "returns both for a user that's both a student and a teacher" do
|
||||
course_with_ta(:course => course, :user => @student, :active_all => true)
|
||||
as_student, as_instructor = overridable.lock_ats_for(@student)
|
||||
as_student.should_not be_nil
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "uses the overridden lock date as the applicable lock date" do
|
||||
as_student, _ = overridable.lock_ats_for(@student)
|
||||
as_student.should == { :lock_at => override.lock_at }
|
||||
end
|
||||
|
||||
it "includes the base lock date in the list of lock dates" do
|
||||
_, as_instructor = overridable.lock_ats_for(@teacher)
|
||||
as_instructor.should include({ :base => true, :lock_at => overridable.lock_at })
|
||||
end
|
||||
|
||||
it "includes visible lock date overrides in the list of lock dates" do
|
||||
_, as_instructor = overridable.lock_ats_for(@teacher)
|
||||
as_instructor.detect { |a| a[:override].present? }.should == {
|
||||
:title => course.default_section.name,
|
||||
:lock_at => override.lock_at,
|
||||
:override => override
|
||||
}
|
||||
end
|
||||
|
||||
it "excludes visible overrides that don't override lock_at from the list of lock dates" do
|
||||
override.clear_lock_at_override
|
||||
override.save!
|
||||
|
||||
_, as_instructor = overridable.lock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
|
||||
it "excludes overrides that aren't visible from the list of lock dates" do
|
||||
@enrollment = @teacher.enrollments.first
|
||||
@enrollment.limit_privileges_to_course_section = true
|
||||
@enrollment.save!
|
||||
|
||||
@section2 = course.course_sections.create!
|
||||
override.set = @section2
|
||||
override.save!
|
||||
|
||||
_, as_instructor = overridable.lock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Assignment do
|
||||
it_should_behave_like "an object whose dates are overridable"
|
||||
|
||||
let(:overridable) { assignment_model(:due_at => 5.days.ago) }
|
||||
let(:overridable_type) { :assignment }
|
||||
end
|
||||
|
||||
describe Quiz do
|
||||
it_should_behave_like "an object whose dates are overridable"
|
||||
|
||||
let(:overridable) { quiz_model(:due_at => 5.days.ago) }
|
||||
let(:overridable_type) { :quiz }
|
||||
end
|
|
@ -233,6 +233,12 @@ describe AssignmentOverride do
|
|||
@override.set = @course.default_section
|
||||
@override.should be_valid
|
||||
end
|
||||
|
||||
it "is valid when the assignment is nil if it has a quiz" do
|
||||
@override.assignment = nil
|
||||
@override.quiz = quiz_model
|
||||
@override.should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "title" do
|
||||
|
@ -482,4 +488,54 @@ describe AssignmentOverride do
|
|||
visible_overrides.should include @override2
|
||||
end
|
||||
end
|
||||
|
||||
describe "default_values" do
|
||||
subject { override }
|
||||
|
||||
let(:override) { AssignmentOverride.new }
|
||||
let(:quiz) { Quiz.new }
|
||||
let(:assignment) { Assignment.new }
|
||||
|
||||
context "when the override belongs to a quiz" do
|
||||
before do
|
||||
override.quiz = quiz
|
||||
end
|
||||
|
||||
context "that has an assignment" do
|
||||
it "uses the quiz's assignment" do
|
||||
override.quiz.assignment = assignment
|
||||
override.send(:default_values)
|
||||
override.assignment.should == assignment
|
||||
end
|
||||
end
|
||||
|
||||
context "that has no assignment" do
|
||||
it "has a nil assignment" do
|
||||
override.send(:default_values)
|
||||
override.assignment.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the override belongs to an assignment" do
|
||||
before do
|
||||
override.assignment = assignment
|
||||
end
|
||||
|
||||
context "that has a quiz" do
|
||||
it "uses the assignment's quiz" do
|
||||
override.assignment.quiz = quiz
|
||||
override.send(:default_values)
|
||||
override.quiz.should == quiz
|
||||
end
|
||||
end
|
||||
|
||||
context "that has no quiz" do
|
||||
it "has a nil quiz" do
|
||||
override.send(:default_values)
|
||||
override.quiz.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,4 +78,46 @@ describe AssignmentOverrideStudent do
|
|||
@override_student.valid? # trigger maintenance
|
||||
@override_student.assignment_id.should == @override2.assignment_id
|
||||
end
|
||||
|
||||
describe "default_values" do
|
||||
let(:override_student) { AssignmentOverrideStudent.new }
|
||||
let(:override) { AssignmentOverride.new }
|
||||
let(:quiz_id) { 1 }
|
||||
let(:assignment_id) { 2 }
|
||||
|
||||
before do
|
||||
override_student.assignment_override = override
|
||||
end
|
||||
|
||||
context "when the override has an assignment" do
|
||||
before do
|
||||
override.assignment_id = assignment_id
|
||||
override_student.send(:default_values)
|
||||
end
|
||||
|
||||
it "has the assignment's ID" do
|
||||
override_student.assignment_id.should == assignment_id
|
||||
end
|
||||
|
||||
it "has a nil quiz ID" do
|
||||
override_student.quiz_id.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when the override has a quiz and assignment" do
|
||||
before do
|
||||
override.assignment_id = assignment_id
|
||||
override.quiz_id = quiz_id
|
||||
override_student.send(:default_values)
|
||||
end
|
||||
|
||||
it "has the assignment's ID" do
|
||||
override_student.assignment_id.should == assignment_id
|
||||
end
|
||||
|
||||
it "has the quiz's ID" do
|
||||
override_student.quiz_id.should == quiz_id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -598,357 +598,6 @@ describe Assignment do
|
|||
end
|
||||
end
|
||||
|
||||
it "should respond to #overridden_for(user)" do
|
||||
student_in_course
|
||||
|
||||
@assignment = assignment_model(:course => @course, :due_at => 5.days.from_now)
|
||||
@assignment.reload
|
||||
|
||||
@override = assignment_override_model(:assignment => @assignment)
|
||||
@override.override_due_at(7.days.from_now)
|
||||
@override.save!
|
||||
@override.reload
|
||||
|
||||
@override_student = @override.assignment_override_students.build
|
||||
@override_student.user = @student
|
||||
@override_student.save!
|
||||
|
||||
@overridden = @assignment.overridden_for(@student)
|
||||
@overridden.due_at.should == @override.due_at
|
||||
end
|
||||
|
||||
describe "has_overrides?" do
|
||||
let(:assignment) { assignment_model(:course => @course, :due_at => 5.days.from_now) }
|
||||
|
||||
it "returns true when it does" do
|
||||
assignment_override_model(:assignment => assignment)
|
||||
assignment.has_overrides?.should be_true
|
||||
end
|
||||
|
||||
it "returns false when it doesn't" do
|
||||
assignment.has_overrides?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#overrides_visible_to(user)" do
|
||||
before :each do
|
||||
@assignment = assignment_model
|
||||
@override = assignment_override_model(:assignment => @assignment)
|
||||
@override.set = @course.default_section
|
||||
@override.save!
|
||||
end
|
||||
|
||||
it "should delegate to visible_to on the active overrides by default" do
|
||||
@expected_value = stub("expected value")
|
||||
@assignment.active_assignment_overrides.expects(:visible_to).with(@teacher, @course).returns(@expected_value)
|
||||
@assignment.overrides_visible_to(@teacher).should == @expected_value
|
||||
end
|
||||
|
||||
it "should allow overriding the scope" do
|
||||
@override.destroy
|
||||
@assignment.overrides_visible_to(@teacher).should be_empty
|
||||
@assignment.overrides_visible_to(@teacher, @assignment.assignment_overrides(true)).should == [@override]
|
||||
end
|
||||
|
||||
it "should skip the visible_to application if the scope is already empty" do
|
||||
@override.destroy
|
||||
@assignment.active_assignment_overrides.expects(:visible_to).times(0)
|
||||
@assignment.overrides_visible_to(@teacher)
|
||||
end
|
||||
|
||||
it "should return a scope" do
|
||||
# can't use "should respond_to", because that delegates to the instantiated Array
|
||||
lambda{ @assignment.overrides_visible_to(@teacher).scoped({}) }.should_not raise_exception
|
||||
end
|
||||
end
|
||||
|
||||
describe "#due_dates_for(user)" do
|
||||
before :each do
|
||||
course_with_student(:active_all => true)
|
||||
|
||||
@assignment = assignment_model(:course => @course, :due_at => 5.days.ago)
|
||||
@assignment.reload
|
||||
|
||||
@override = assignment_override_model(:assignment => @assignment)
|
||||
@override.set = @course.default_section
|
||||
@override.override_due_at(2.days.ago)
|
||||
@override.save!
|
||||
@override.reload
|
||||
end
|
||||
|
||||
it "should not return the list of due dates for a student" do
|
||||
_, as_instructor = @assignment.due_dates_for(@student)
|
||||
as_instructor.should be_nil
|
||||
end
|
||||
|
||||
it "should not return an applicable due date for a teacher" do
|
||||
as_student, _ = @assignment.due_dates_for(@teacher)
|
||||
as_student.should be_nil
|
||||
end
|
||||
|
||||
it "should return the applicable due date for a student" do
|
||||
as_student, _ = @assignment.due_dates_for(@student)
|
||||
as_student.should_not be_nil
|
||||
end
|
||||
|
||||
it "should return the list of due dates for a teacher" do
|
||||
_, as_instructor = @assignment.due_dates_for(@teacher)
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "should return both for a user that's both a student and a teacher" do
|
||||
course_with_ta(:course => @course, :user => @student, :active_all => true)
|
||||
as_student, as_instructor = @assignment.due_dates_for(@student)
|
||||
as_student.should_not be_nil
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "should use the overridden due date as the applicable due date" do
|
||||
as_student, _ = @assignment.due_dates_for(@student)
|
||||
as_student[:due_at].should == @override.due_at
|
||||
as_student[:all_day].should == @override.all_day
|
||||
as_student[:all_day_date].should == @override.all_day_date
|
||||
end
|
||||
|
||||
it "should include the base due date in the list of due dates" do
|
||||
_, as_instructor = @assignment.due_dates_for(@teacher)
|
||||
as_instructor.should include({
|
||||
:base => true,
|
||||
:due_at => @assignment.due_at,
|
||||
:all_day => @assignment.all_day,
|
||||
:all_day_date => @assignment.all_day_date
|
||||
})
|
||||
end
|
||||
|
||||
it "should include visible due date overrides in the list of due dates" do
|
||||
_, as_instructor = @assignment.due_dates_for(@teacher)
|
||||
as_instructor.should include({
|
||||
:title => @course.default_section.name,
|
||||
:due_at => @override.due_at,
|
||||
:all_day => @override.all_day,
|
||||
:all_day_date => @override.all_day_date,
|
||||
:override => @override
|
||||
})
|
||||
end
|
||||
|
||||
it "should exclude visible overrides that don't override due_at from the list of due dates" do
|
||||
@override.clear_due_at_override
|
||||
@override.save!
|
||||
|
||||
_, as_instructor = @assignment.due_dates_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
|
||||
it "should exclude overrides that aren't visible from the list of due dates" do
|
||||
@enrollment = @teacher.enrollments.first
|
||||
@enrollment.limit_privileges_to_course_section = true
|
||||
@enrollment.save!
|
||||
|
||||
@section2 = @course.course_sections.create!
|
||||
@override.set = @section2
|
||||
@override.save!
|
||||
|
||||
_, as_instructor = @assignment.due_dates_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "due_date_hash" do
|
||||
it "returns the due at, all day, and all day date params" do
|
||||
due = 5.days.from_now
|
||||
a = Assignment.new(:due_at => due)
|
||||
a.due_date_hash.should == { :due_at => due, :all_day => false, :all_day_date => nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe "observed_student_due_dates" do
|
||||
it "returns a list of overridden due date hashes" do
|
||||
a = Assignment.new
|
||||
u = User.new
|
||||
student1, student2 = [mock, mock]
|
||||
|
||||
{ student1 => '1', student2 => '2' }.each do |student, value|
|
||||
a.expects(:overridden_for).with(student).returns \
|
||||
mock(:due_date_hash => { :student => value })
|
||||
end
|
||||
|
||||
ObserverEnrollment.expects(:observed_students).returns({student1 => [], student2 => []})
|
||||
|
||||
override_hashes = a.observed_student_due_dates(u).sort_by { |h| h[:student] }
|
||||
override_hashes.should == [ { :student => '1' }, { :student => '2' } ]
|
||||
end
|
||||
end
|
||||
|
||||
describe "#unlock_ats_for(user)" do
|
||||
before :each do
|
||||
course_with_student(:active_all => true)
|
||||
|
||||
@assignment = assignment_model(:course => @course, :unlock_at => 2.days.ago)
|
||||
@assignment.reload
|
||||
|
||||
@override = assignment_override_model(:assignment => @assignment)
|
||||
@override.set = @course.default_section
|
||||
@override.override_unlock_at(5.days.ago)
|
||||
@override.save!
|
||||
@override.reload
|
||||
end
|
||||
|
||||
it "should not return the list of unlock dates for a student" do
|
||||
_, as_instructor = @assignment.unlock_ats_for(@student)
|
||||
as_instructor.should be_nil
|
||||
end
|
||||
|
||||
it "should not return an applicable unlock date for a teacher" do
|
||||
as_student, _ = @assignment.unlock_ats_for(@teacher)
|
||||
as_student.should be_nil
|
||||
end
|
||||
|
||||
it "should return the applicable unlock date for a student" do
|
||||
as_student, _ = @assignment.unlock_ats_for(@student)
|
||||
as_student.should_not be_nil
|
||||
end
|
||||
|
||||
it "should return the list of unlock dates for a teacher" do
|
||||
_, as_instructor = @assignment.unlock_ats_for(@teacher)
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "should return both for a user that's both a student and a teacher" do
|
||||
course_with_ta(:course => @course, :user => @student, :active_all => true)
|
||||
as_student, as_instructor = @assignment.unlock_ats_for(@student)
|
||||
as_student.should_not be_nil
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "should use the overridden unlock date as the applicable unlock date" do
|
||||
as_student, _ = @assignment.unlock_ats_for(@student)
|
||||
as_student.should == { :unlock_at => @override.unlock_at }
|
||||
end
|
||||
|
||||
it "should include the base unlock date in the list of unlock dates" do
|
||||
_, as_instructor = @assignment.unlock_ats_for(@teacher)
|
||||
as_instructor.should include({ :base => true, :unlock_at => @assignment.unlock_at })
|
||||
end
|
||||
|
||||
it "should include visible unlock date overrides in the list of unlock dates" do
|
||||
_, as_instructor = @assignment.unlock_ats_for(@teacher)
|
||||
as_instructor.should include({
|
||||
:title => @course.default_section.name,
|
||||
:unlock_at => @override.unlock_at,
|
||||
:override => @override
|
||||
})
|
||||
end
|
||||
|
||||
it "should exclude visible overrides that don't override unlock_at from the list of unlock dates" do
|
||||
@override.clear_unlock_at_override
|
||||
@override.save!
|
||||
|
||||
_, as_instructor = @assignment.unlock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
|
||||
it "should exclude overrides that aren't visible from the list of unlock dates" do
|
||||
@enrollment = @teacher.enrollments.first
|
||||
@enrollment.limit_privileges_to_course_section = true
|
||||
@enrollment.save!
|
||||
|
||||
@section2 = @course.course_sections.create!
|
||||
@override.set = @section2
|
||||
@override.save!
|
||||
|
||||
_, as_instructor = @assignment.unlock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "#lock_ats_for(user)" do
|
||||
before :each do
|
||||
course_with_student(:active_all => true)
|
||||
|
||||
@assignment = assignment_model(:course => @course, :lock_at => 5.days.ago)
|
||||
@assignment.reload
|
||||
|
||||
@override = assignment_override_model(:assignment => @assignment)
|
||||
@override.set = @course.default_section
|
||||
@override.override_lock_at(2.days.ago)
|
||||
@override.save!
|
||||
@override.reload
|
||||
end
|
||||
|
||||
it "should not return the list of lock dates for a student" do
|
||||
_, as_instructor = @assignment.lock_ats_for(@student)
|
||||
as_instructor.should be_nil
|
||||
end
|
||||
|
||||
it "should not return an applicable lock date for a teacher" do
|
||||
as_student, _ = @assignment.lock_ats_for(@teacher)
|
||||
as_student.should be_nil
|
||||
end
|
||||
|
||||
it "should return the applicable lock date for a student" do
|
||||
as_student, _ = @assignment.lock_ats_for(@student)
|
||||
as_student.should_not be_nil
|
||||
end
|
||||
|
||||
it "should return the list of lock dates for a teacher" do
|
||||
_, as_instructor = @assignment.lock_ats_for(@teacher)
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "should return both for a user that's both a student and a teacher" do
|
||||
course_with_ta(:course => @course, :user => @student, :active_all => true)
|
||||
as_student, as_instructor = @assignment.lock_ats_for(@student)
|
||||
as_student.should_not be_nil
|
||||
as_instructor.should_not be_nil
|
||||
end
|
||||
|
||||
it "should use the overridden lock date as the applicable lock date" do
|
||||
as_student, _ = @assignment.lock_ats_for(@student)
|
||||
as_student.should == { :lock_at => @override.lock_at }
|
||||
end
|
||||
|
||||
it "should include the base lock date in the list of lock dates" do
|
||||
_, as_instructor = @assignment.lock_ats_for(@teacher)
|
||||
as_instructor.should include({ :base => true, :lock_at => @assignment.lock_at })
|
||||
end
|
||||
|
||||
it "should include visible lock date overrides in the list of lock dates" do
|
||||
_, as_instructor = @assignment.lock_ats_for(@teacher)
|
||||
as_instructor.detect { |a| a[:override].present? }.should == {
|
||||
:title => @course.default_section.name,
|
||||
:lock_at => @override.lock_at,
|
||||
:override => @override
|
||||
}
|
||||
end
|
||||
|
||||
it "should exclude visible overrides that don't override lock_at from the list of lock dates" do
|
||||
@override.clear_lock_at_override
|
||||
@override.save!
|
||||
|
||||
_, as_instructor = @assignment.lock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
|
||||
it "should exclude overrides that aren't visible from the list of lock dates" do
|
||||
@enrollment = @teacher.enrollments.first
|
||||
@enrollment.limit_privileges_to_course_section = true
|
||||
@enrollment.save!
|
||||
|
||||
@section2 = @course.course_sections.create!
|
||||
@override.set = @section2
|
||||
@override.save!
|
||||
|
||||
_, as_instructor = @assignment.lock_ats_for(@teacher)
|
||||
as_instructor.size.should == 1
|
||||
as_instructor.first[:base].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context "concurrent inserts" do
|
||||
def concurrent_inserts
|
||||
assignment_model
|
||||
|
@ -2679,6 +2328,78 @@ describe Assignment do
|
|||
@assignment.submitted_count.should == 50
|
||||
end
|
||||
end
|
||||
|
||||
describe "linking overrides with quizzes" do
|
||||
let(:course) { course_model }
|
||||
let(:assignment) { assignment_model(:course => course, :due_at => 5.days.from_now).reload }
|
||||
let(:override) { assignment_override_model(:assignment => assignment) }
|
||||
let(:override_student) { override.assignment_override_students.build }
|
||||
|
||||
before do
|
||||
override.override_due_at(7.days.from_now)
|
||||
override.save!
|
||||
|
||||
student_in_course(:course => course)
|
||||
override_student.user = @student
|
||||
override_student.save!
|
||||
end
|
||||
|
||||
context "before the assignment has a quiz" do
|
||||
context "override" do
|
||||
it "has a nil quiz" do
|
||||
override.quiz.should be_nil
|
||||
end
|
||||
|
||||
it "has an assignment" do
|
||||
override.assignment.should == assignment
|
||||
end
|
||||
end
|
||||
|
||||
context "override student" do
|
||||
it "has a nil quiz" do
|
||||
override_student.quiz.should be_nil
|
||||
end
|
||||
|
||||
it "has an assignment" do
|
||||
override_student.assignment.should == assignment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "once the assignment changes to a quiz submission" do
|
||||
before do
|
||||
assignment.submission_types = "online_quiz"
|
||||
assignment.save
|
||||
assignment.reload
|
||||
override.reload
|
||||
override_student.reload
|
||||
end
|
||||
|
||||
it "has a quiz" do
|
||||
assignment.quiz.should be_present
|
||||
end
|
||||
|
||||
context "override" do
|
||||
it "has an assignment" do
|
||||
override.assignment.should == assignment
|
||||
end
|
||||
|
||||
it "has the assignment's quiz" do
|
||||
override.quiz.should == assignment.quiz
|
||||
end
|
||||
end
|
||||
|
||||
context "override student" do
|
||||
it "has an assignment" do
|
||||
override_student.assignment.should == assignment
|
||||
end
|
||||
|
||||
it "has the assignment's quiz" do
|
||||
override_student.quiz.should == assignment.quiz
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def setup_assignment_with_group
|
||||
|
|
|
@ -969,4 +969,83 @@ describe Quiz do
|
|||
@quiz.has_student_submissions?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
describe "linking overrides with assignments" do
|
||||
let(:course) { course_model }
|
||||
let(:quiz) { quiz_model(:course => course, :due_at => 5.days.from_now).reload }
|
||||
let(:override) { assignment_override_model(:quiz => quiz) }
|
||||
let(:override_student) { override.assignment_override_students.build }
|
||||
|
||||
before do
|
||||
override.override_due_at(7.days.from_now)
|
||||
override.save!
|
||||
|
||||
student_in_course(:course => course)
|
||||
override_student.user = @student
|
||||
override_student.save!
|
||||
end
|
||||
|
||||
context "before the quiz has an assignment" do
|
||||
context "override" do
|
||||
it "has a quiz" do
|
||||
override.quiz.should == quiz
|
||||
end
|
||||
|
||||
it "has a nil assignment" do
|
||||
override.assignment.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "override student" do
|
||||
it "has a quiz" do
|
||||
override_student.quiz.should == quiz
|
||||
end
|
||||
|
||||
it "has a nil assignment" do
|
||||
override_student.assignment.should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "once the quiz is published" do
|
||||
before do
|
||||
# publish the quiz
|
||||
quiz.workflow_state = 'available'
|
||||
quiz.save
|
||||
override.reload
|
||||
override_student.reload
|
||||
end
|
||||
|
||||
context "override" do
|
||||
it "has a quiz" do
|
||||
override.quiz.should == quiz
|
||||
end
|
||||
|
||||
it "has the quiz's assignment" do
|
||||
override.assignment.should == quiz.assignment
|
||||
end
|
||||
end
|
||||
|
||||
context "override student" do
|
||||
it "has a quiz" do
|
||||
override_student.quiz.should == quiz
|
||||
end
|
||||
|
||||
it "has the quiz's assignment" do
|
||||
override_student.assignment.should == quiz.assignment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when the assignment ID doesn't change" do
|
||||
it "doesn't update overrides" do
|
||||
quiz.expects(:link_assignment_overrides).once
|
||||
# publish the quiz
|
||||
quiz.workflow_state = 'available'
|
||||
quiz.save
|
||||
quiz.expects(:link_assignment_overrides).never
|
||||
quiz.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue