VDD: assignment override data structures
refs #10831 test-plan: - run specs. this is all infrastructure, no real separate test plan. Change-Id: Ic67f574b7e4cbffd114f6ed34d306a393a6bd93c Reviewed-on: https://gerrit.instructure.com/14117 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
parent
dbc1d7f59b
commit
6fc0e64581
|
@ -50,6 +50,7 @@ class Assignment < ActiveRecord::Base
|
|||
belongs_to :grading_standard
|
||||
belongs_to :group_category
|
||||
has_many :assignment_reminders, :dependent => :destroy
|
||||
has_many :assignment_overrides, :dependent => :destroy
|
||||
|
||||
has_one :external_tool_tag, :class_name => 'ContentTag', :as => :context, :dependent => :destroy
|
||||
validates_associated :external_tool_tag, :if => :external_tool?
|
||||
|
@ -128,7 +129,16 @@ class Assignment < ActiveRecord::Base
|
|||
:clear_unannounced_grading_changes_if_just_unpublished,
|
||||
:schedule_do_auto_peer_review_job_if_automatic_peer_review,
|
||||
:delete_empty_abandoned_children,
|
||||
:remove_assignment_updated_flag
|
||||
:remove_assignment_updated_flag,
|
||||
:validate_assignment_overrides
|
||||
|
||||
def validate_assignment_overrides
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
||||
def schedule_do_auto_peer_review_job_if_automatic_peer_review
|
||||
if peer_reviews && automatic_peer_reviews && !peer_reviews_assigned
|
||||
|
@ -213,26 +223,41 @@ class Assignment < ActiveRecord::Base
|
|||
write_attribute :turnitin_settings, settings
|
||||
end
|
||||
|
||||
def self.all_day_interpretation(opts={})
|
||||
if opts[:due_at]
|
||||
if opts[:due_at] == opts[:due_at_was]
|
||||
# (comparison is modulo time zone) no real change, leave as was
|
||||
return opts[:all_day_was], opts[:all_day_date_was]
|
||||
else
|
||||
# 'normal' case. compare due_at to fancy midnight and extract its
|
||||
# date-part
|
||||
return (opts[:due_at].strftime("%H:%M") == '23:59'), opts[:due_at].to_date
|
||||
end
|
||||
else
|
||||
# no due at = all_day and all_day_date are irrelevant
|
||||
return nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
def default_values
|
||||
raise "Assignments can only be assigned to Course records" if self.context_type && self.context_type != "Course"
|
||||
self.context_code = "#{self.context_type.underscore}_#{self.context_id}"
|
||||
self.title ||= (self.assignment_group.default_assignment_name rescue nil) || "Assignment"
|
||||
self.grading_type = "pass_fail" if self.submission_types == "attendance"
|
||||
zoned_due_at = self.due_at && ActiveSupport::TimeWithZone.new(self.due_at.utc, (ActiveSupport::TimeZone.new(self.time_zone_edited) rescue nil) || Time.zone)
|
||||
if self.due_at_changed?
|
||||
if zoned_due_at && zoned_due_at.strftime("%H:%M") == '23:59'
|
||||
self.all_day = true
|
||||
elsif self.due_at && self.due_at_was && self.all_day && self.due_at.strftime("%H:%M") == self.due_at_was.strftime("%H:%M")
|
||||
self.all_day = true
|
||||
else
|
||||
self.all_day = false
|
||||
end
|
||||
end
|
||||
|
||||
# make the comparison to "fancy midnight" and the date-part extraction in
|
||||
# the time zone that was active during editing
|
||||
time_zone = (ActiveSupport::TimeZone.new(self.time_zone_edited) rescue nil) || Time.zone
|
||||
self.all_day, self.all_day_date = Assignment.all_day_interpretation(
|
||||
:due_at => self.due_at ? self.due_at.in_time_zone(time_zone) : nil,
|
||||
:due_at_was => self.due_at_was,
|
||||
:all_day_was => self.all_day_was,
|
||||
:all_day_date_was => self.all_day_date_was)
|
||||
|
||||
if !self.assignment_group || (self.assignment_group.deleted? && !self.deleted?)
|
||||
self.assignment_group = self.context.assignment_groups.active.first || self.context.assignment_groups.create!
|
||||
end
|
||||
self.mastery_score = [self.mastery_score, self.points_possible].min if self.mastery_score && self.points_possible
|
||||
self.all_day_date = (zoned_due_at.to_date rescue nil) if !self.all_day_date || self.due_at_changed? || self.all_day_date_changed?
|
||||
self.submission_types ||= "none"
|
||||
self.peer_reviews_assign_at = [self.due_at, self.peer_reviews_assign_at].compact.max
|
||||
@workflow_state_was = self.workflow_state_was
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
class AssignmentOverride < ActiveRecord::Base
|
||||
include Workflow
|
||||
include TextHelper
|
||||
|
||||
simply_versioned :keep => 5
|
||||
|
||||
attr_accessible
|
||||
|
||||
belongs_to :assignment
|
||||
belongs_to :set, :polymorphic => true
|
||||
has_many :assignment_override_students, :dependent => :destroy
|
||||
|
||||
validates_presence_of :assignment, :assignment_version, :title
|
||||
validates_inclusion_of :set_type, :in => %w(CourseSection Group ADHOC)
|
||||
validates_length_of :title, :maximum => maximum_string_length, :allow_nil => true
|
||||
|
||||
concrete_set = lambda{ |override| ['CourseSection', 'Group'].include?(override.set_type) }
|
||||
|
||||
validates_presence_of :set, :set_id, :if => concrete_set
|
||||
validates_uniqueness_of :set_id, :scope => [:assignment_id, :set_type, :workflow_state], :if => lambda{ |override| override.active? && concrete_set.call(override) }
|
||||
validate :if => concrete_set do |record|
|
||||
if record.set && record.assignment
|
||||
case record.set
|
||||
when CourseSection
|
||||
record.errors.add :set, "not from assignment's course" unless record.set.course_id == record.assignment.context_id
|
||||
when Group
|
||||
record.errors.add :set, "not from assignment's group category" unless record.deleted? || record.set.group_category_id == record.assignment.group_category_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
validate :set_id, :unless => concrete_set do |record|
|
||||
if record.set_type == 'ADHOC' && !record.set_id.nil?
|
||||
record.errors.add :set_id, "must be nil with set_type ADHOC"
|
||||
end
|
||||
end
|
||||
|
||||
workflow do
|
||||
state :active
|
||||
state :deleted
|
||||
end
|
||||
|
||||
alias_method :destroy!, :destroy
|
||||
def destroy
|
||||
self.workflow_state = 'deleted'
|
||||
self.save!
|
||||
end
|
||||
|
||||
named_scope :active, lambda {
|
||||
{:conditions => {:workflow_state => 'active'} }
|
||||
}
|
||||
|
||||
before_validation :default_values
|
||||
def default_values
|
||||
self.set_type ||= 'ADHOC'
|
||||
self.assignment_version = assignment.version_number if assignment
|
||||
self.title = set.name if set
|
||||
end
|
||||
protected :default_values
|
||||
|
||||
# override set read accessor and set_id read/write accessors so that reading
|
||||
# set/set_id or setting set_id while set_type=ADHOC doesn't try and find the
|
||||
# ADHOC model
|
||||
def set_id
|
||||
read_attribute(:set_id)
|
||||
end
|
||||
|
||||
def set_with_adhoc
|
||||
if self.set_type == 'ADHOC'
|
||||
nil
|
||||
else
|
||||
set_without_adhoc
|
||||
end
|
||||
end
|
||||
alias_method_chain :set, :adhoc
|
||||
|
||||
def set_id=(id)
|
||||
if self.set_type == 'ADHOC'
|
||||
write_attribute(:set_id, id)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def self.override(field)
|
||||
define_method "override_#{field}" do |value|
|
||||
send("#{field}_overridden=", true)
|
||||
send("#{field}=", value)
|
||||
end
|
||||
|
||||
define_method "clear_#{field}_override" do
|
||||
send("#{field}_overridden=", false)
|
||||
send("#{field}=", nil)
|
||||
end
|
||||
|
||||
validates_inclusion_of "#{field}_overridden", :in => [false, true]
|
||||
before_validation do |override|
|
||||
if override.send("#{field}_overridden").nil?
|
||||
override.send("#{field}_overridden=", false)
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
override :due_at
|
||||
override :unlock_at
|
||||
override :lock_at
|
||||
|
||||
def due_at=(new_due_at)
|
||||
new_all_day, new_all_day_date = Assignment.all_day_interpretation(
|
||||
:due_at => new_due_at,
|
||||
:due_at_was => read_attribute(:due_at),
|
||||
:all_day_was => read_attribute(:all_day),
|
||||
:all_day_date_was => read_attribute(:all_day_date))
|
||||
|
||||
write_attribute(:due_at, new_due_at)
|
||||
write_attribute(:all_day, new_all_day)
|
||||
write_attribute(:all_day_date, new_all_day_date)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,52 @@
|
|||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
class AssignmentOverrideStudent < ActiveRecord::Base
|
||||
belongs_to :assignment
|
||||
belongs_to :assignment_override
|
||||
belongs_to :user
|
||||
|
||||
attr_accessible
|
||||
|
||||
validates_presence_of :assignment, :assignment_override, :user
|
||||
validates_uniqueness_of :user_id, :scope => :assignment_id
|
||||
|
||||
validate :assignment_override do |record|
|
||||
if record.assignment_override && record.assignment_override.set_type != 'ADHOC'
|
||||
record.errors.add :assignment_override, "is not adhoc"
|
||||
end
|
||||
end
|
||||
|
||||
validate :assignment do |record|
|
||||
if record.assignment_override && record.assignment_id != record.assignment_override.assignment_id
|
||||
record.errors.add :assignment, "doesn't match assignment_override"
|
||||
end
|
||||
end
|
||||
|
||||
validate :user do |record|
|
||||
if record.user && record.assignment && record.user.student_enrollments.scoped(:conditions => {:course_id => record.assignment.context_id}).first.nil?
|
||||
record.errors.add :user, "is not in the assignment's course"
|
||||
end
|
||||
end
|
||||
|
||||
before_validation :default_values
|
||||
def default_values
|
||||
self.assignment_id = self.assignment_override.assignment_id if self.assignment_override
|
||||
end
|
||||
protected :default_values
|
||||
end
|
|
@ -0,0 +1,65 @@
|
|||
class AssignmentOverrideMigration < ActiveRecord::Migration
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
create_table :assignment_overrides do |t|
|
||||
t.timestamps
|
||||
|
||||
# generic info
|
||||
t.integer :assignment_id, :null => false, :limit => 8
|
||||
t.integer :assignment_version, :null => false
|
||||
t.string :set_type, :null => :false
|
||||
t.integer :set_id, :limit => 8
|
||||
t.string :title
|
||||
t.string :workflow_state, :null => false
|
||||
|
||||
# due at override
|
||||
t.boolean :due_at_overridden, :default => false, :null => false
|
||||
t.datetime :due_at
|
||||
t.boolean :all_day
|
||||
t.date :all_day_date
|
||||
|
||||
# unlock at override
|
||||
t.boolean :unlock_at_overridden, :default => false, :null => false
|
||||
t.datetime :unlock_at
|
||||
|
||||
# lock at override
|
||||
t.boolean :lock_at_overridden, :default => false, :null => false
|
||||
t.datetime :lock_at
|
||||
end
|
||||
|
||||
if connection.adapter_name =~ /\Apostgresql/i
|
||||
add_index :assignment_overrides, [:assignment_id, :set_type, :set_id],
|
||||
:name => 'index_assignment_overrides_on_assignment_and_set',
|
||||
:unique => true,
|
||||
:conditions => "workflow_state='active' and set_id is not null"
|
||||
else
|
||||
# can't enforce unique without conditions, since when set_type is 'adhoc'
|
||||
# and set_id null, there may be multiple overrides
|
||||
add_index :assignment_overrides, [:assignment_id, :set_type, :set_id],
|
||||
:name => 'index_assignment_overrides_on_assignment_and_set'
|
||||
end
|
||||
|
||||
add_foreign_key :assignment_overrides, :assignments
|
||||
|
||||
create_table :assignment_override_students do |t|
|
||||
t.timestamps
|
||||
|
||||
t.integer :assignment_id, :null => false, :limit => 8
|
||||
t.integer :assignment_override_id, :null => false, :limit => 8
|
||||
t.integer :user_id, :null => false, :limit => 8
|
||||
end
|
||||
|
||||
add_index :assignment_override_students, [:assignment_id, :user_id], :unique => true
|
||||
add_index :assignment_override_students, :assignment_override_id
|
||||
|
||||
add_foreign_key :assignment_override_students, :assignments
|
||||
add_foreign_key :assignment_override_students, :assignment_overrides
|
||||
add_foreign_key :assignment_override_students, :users
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :assignment_override_students
|
||||
drop_table :assignment_overrides
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
#
|
||||
# Copyright (C) 2011 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/>.
|
||||
#
|
||||
|
||||
def assignment_override_model(opts={})
|
||||
assignment = opts.delete(:assignment) || assignment_model(opts)
|
||||
@override = factory_with_protected_attributes(assignment.assignment_overrides, assignment_override_valid_attributes.merge(opts))
|
||||
@override
|
||||
end
|
||||
|
||||
def assignment_override_valid_attributes
|
||||
{ :title => "Some Title" }
|
||||
end
|
|
@ -0,0 +1,341 @@
|
|||
#
|
||||
# Copyright (C) 2011 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')
|
||||
|
||||
describe AssignmentOverride do
|
||||
it "should soft-delete" do
|
||||
@override = assignment_override_model
|
||||
@override.destroy
|
||||
@override = AssignmentOverride.find_by_id(@override.id)
|
||||
@override.should_not be_nil
|
||||
@override.workflow_state.should == 'deleted'
|
||||
end
|
||||
|
||||
it "should default set_type to adhoc" do
|
||||
@override = assignment_override_model
|
||||
@override.valid? # trigger bookkeeping
|
||||
@override.set_type.should == 'ADHOC'
|
||||
end
|
||||
|
||||
it "should allow reading set and set_id when set_type is adhoc" do
|
||||
@override = assignment_override_model
|
||||
@override.set_type = 'ADHOC'
|
||||
@override.set.should be_nil
|
||||
@override.set_id.should be_nil
|
||||
end
|
||||
|
||||
it "should be versioned" do
|
||||
@override = assignment_override_model
|
||||
@override.should respond_to :version_number
|
||||
old_version = @override.version_number
|
||||
@override.override_due_at(5.days.from_now)
|
||||
@override.save!
|
||||
@override.version_number.should_not == old_version
|
||||
end
|
||||
|
||||
it "should keep its assignment version up to date" do
|
||||
@override = assignment_override_model
|
||||
|
||||
@override.valid? # trigger bookkeeping
|
||||
@override.assignment_version.should == @override.assignment.version_number
|
||||
|
||||
old_version = @override.assignment.version_number
|
||||
@override.assignment.due_at = 5.days.from_now
|
||||
@override.assignment.save!
|
||||
@override.assignment.version_number.should_not == old_version
|
||||
|
||||
@override.valid? # trigger bookkeeping
|
||||
@override.assignment_version.should == @override.assignment.version_number
|
||||
end
|
||||
|
||||
describe "active scope" do
|
||||
it "should include active overrides" do
|
||||
5.times.map{ assignment_override_model }
|
||||
AssignmentOverride.active.count.should == 5
|
||||
end
|
||||
|
||||
it "should exclude deleted overrides" do
|
||||
5.times.map{ assignment_override_model.destroy }
|
||||
AssignmentOverride.active.count.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "validations" do
|
||||
before :each do
|
||||
@override = assignment_override_model
|
||||
@override.should be_valid
|
||||
end
|
||||
|
||||
def invalid_id_for_model(model)
|
||||
(model.scoped(:select => 'max(id) as id').first.id || 0) + 1
|
||||
end
|
||||
|
||||
it "should reject non-nil set_id with an adhoc set" do
|
||||
@override.set_id = 1
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject an empty title with an adhoc set" do
|
||||
@override.title = nil
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject an empty assignment" do
|
||||
@override.assignment = nil
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject an invalid assignment" do
|
||||
@override.assignment = nil
|
||||
@override.assignment_id = invalid_id_for_model(Assignment)
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should accept section sets" do
|
||||
@override.set = @course.course_sections.create!
|
||||
@override.should be_valid
|
||||
end
|
||||
|
||||
it "should accept group sets" do
|
||||
@category = @course.group_categories.create!
|
||||
@override.assignment.group_category = @category
|
||||
@override.set = @category.groups.create!
|
||||
@override.should be_valid
|
||||
end
|
||||
|
||||
it "should reject an empty set_id with a non-adhoc set_type" do
|
||||
@override.set = nil
|
||||
@override.set_type = 'CourseSection'
|
||||
@override.set_id = nil
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject an invalid set_id with a non-adhoc set_type" do
|
||||
@override.set = nil
|
||||
@override.set_type = 'CourseSection'
|
||||
@override.set_id = invalid_id_for_model(CourseSection)
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject sections in different course than assignment" do
|
||||
@other_course = course_model
|
||||
@override.set = @other_course.default_section
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject groups in different category than assignment" do
|
||||
@assignment.group_category = @course.group_categories.create!
|
||||
@category = @course.group_categories.create!
|
||||
@override.set = @category.groups.create
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
# necessary to allow deleting but otherwise keeping assignments that were
|
||||
# for an assignment's previous group category when the assignment's group
|
||||
# category changes
|
||||
it "should not reject groups in different category than assignment when deleted" do
|
||||
@assignment.group_category = @course.group_categories.create!
|
||||
@category = @course.group_categories.create!
|
||||
@override.set = @category.groups.create
|
||||
@override.workflow_state = 'deleted'
|
||||
@override.should be_valid
|
||||
end
|
||||
|
||||
it "should reject unrecognized sets" do
|
||||
@override.set = @override.assignment.context
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject duplicate sets" do
|
||||
@override.set = @course.default_section
|
||||
@override.save!
|
||||
|
||||
@override = AssignmentOverride.new
|
||||
@override.assignment = @assignment
|
||||
@override.set = @course.default_section
|
||||
@override.should_not be_valid
|
||||
end
|
||||
|
||||
it "should allow duplicates of sets where only one is active" do
|
||||
@override.set = @course.default_section
|
||||
@override.save!
|
||||
@override.destroy
|
||||
|
||||
@override = AssignmentOverride.new
|
||||
@override.assignment = @assignment
|
||||
@override.set = @course.default_section
|
||||
@override.should be_valid
|
||||
@override.destroy
|
||||
|
||||
@override = AssignmentOverride.new
|
||||
@override.assignment = @assignment
|
||||
@override.set = @course.default_section
|
||||
@override.should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "title" do
|
||||
before :each do
|
||||
@override = assignment_override_model
|
||||
end
|
||||
|
||||
it "should force title to the name of the section" do
|
||||
@section = @course.default_section
|
||||
@section.name = 'Section Test Value'
|
||||
@override.set = @section
|
||||
@override.title = 'Other Value'
|
||||
@override.valid? # trigger bookkeeping
|
||||
@override.title.should == @section.name
|
||||
end
|
||||
|
||||
it "should default title to the name of the group" do
|
||||
@assignment.group_category = @course.group_categories.create!
|
||||
@group = @assignment.group_category.groups.create!
|
||||
@group.name = 'Group Test Value'
|
||||
@override.set = @group
|
||||
@override.title = 'Other Value'
|
||||
@override.valid? # trigger bookkeeping
|
||||
@override.title.should == @group.name
|
||||
end
|
||||
|
||||
it "should not be changed for adhoc sets" do
|
||||
@override.title = 'Other Value'
|
||||
@override.valid? # trigger bookkeeping
|
||||
@override.title.should == 'Other Value'
|
||||
end
|
||||
end
|
||||
|
||||
def self.describe_override(field, value1, value2)
|
||||
describe "#{field} overrides" do
|
||||
before :each do
|
||||
@assignment = assignment_model(field.to_sym => value1)
|
||||
@override = assignment_override_model(:assignment => @assignment)
|
||||
end
|
||||
|
||||
it "should set the override when a override_#{field} is called" do
|
||||
@override.send("override_#{field}", value2)
|
||||
@override.send("#{field}_overridden").should == true
|
||||
@override.send(field).should == value2
|
||||
end
|
||||
|
||||
it "should clear the override when clear_#{field}_override is called" do
|
||||
@override.send("override_#{field}", value2)
|
||||
@override.send("clear_#{field}_override")
|
||||
@override.send("#{field}_overridden").should == false
|
||||
@override.send(field).should be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe_override("due_at", 5.minutes.from_now, 7.minutes.from_now)
|
||||
describe_override("unlock_at", 5.minutes.from_now, 7.minutes.from_now)
|
||||
describe_override("lock_at", 5.minutes.from_now, 7.minutes.from_now)
|
||||
|
||||
describe "#due_at=" do
|
||||
def fancy_midnight(opts={})
|
||||
zone = opts[:zone] || Time.zone
|
||||
Time.use_zone(zone) do
|
||||
time = opts[:time] || Time.zone.now
|
||||
time.in_time_zone.midnight + 1.day - 1.minute
|
||||
end
|
||||
end
|
||||
|
||||
before :each do
|
||||
@override = assignment_override_model
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with no prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.all_day.should == true
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with same-tz all-day prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska') + 1.day
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.all_day.should == true
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with other-tz all-day prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Baghdad')
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.all_day.should == true
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with non-all-day prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.all_day.should == true
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day no prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
||||
@override.all_day.should == false
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day with same-tz all-day prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@override.all_day.should == false
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day with other-tz all-day prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Baghdad')
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@override.all_day.should == false
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day with non-all-day prior value" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska') + 2.hour
|
||||
@override.all_day.should == false
|
||||
end
|
||||
|
||||
it "should preserve all-day when only changing time zone" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
||||
@override.all_day.should == true
|
||||
end
|
||||
|
||||
it "should preserve non-all-day when only changing time zone" do
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
||||
@override.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@override.all_day.should == false
|
||||
end
|
||||
|
||||
it "should determine date from due_at's timezone" do
|
||||
@override.due_at = Date.today.in_time_zone('Baghdad') + 1.hour # 01:00:00 AST +03:00 today
|
||||
@override.all_day_date.should == Date.today
|
||||
|
||||
@override.due_at = @override.due_at.in_time_zone('Alaska') - 2.hours # 12:00:00 AKDT -08:00 previous day
|
||||
@override.all_day_date.should == Date.today - 1.day
|
||||
end
|
||||
|
||||
it "should preserve all-day date when only changing time zone" do
|
||||
@override.due_at = Date.today.in_time_zone('Baghdad') # 00:00:00 AST +03:00 today
|
||||
@override.due_at = @override.due_at.in_time_zone('Alaska') # 13:00:00 AKDT -08:00 previous day
|
||||
@override.all_day_date.should == Date.today
|
||||
end
|
||||
|
||||
it "should preserve non-all-day date when only changing time zone" do
|
||||
@override.due_at = Date.today.in_time_zone('Alaska') - 11.hours # 13:00:00 AKDT -08:00 previous day
|
||||
@override.due_at = @override.due_at.in_time_zone('Baghdad') # 00:00:00 AST +03:00 today
|
||||
@override.all_day_date.should == Date.today - 1.day
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
#
|
||||
# Copyright (C) 2011 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')
|
||||
|
||||
describe AssignmentOverrideStudent do
|
||||
describe "validations" do
|
||||
before :each do
|
||||
student_in_course
|
||||
@override = assignment_override_model(:course => @course)
|
||||
@override_student = @override.assignment_override_students.build
|
||||
@override_student.user = @student
|
||||
end
|
||||
|
||||
it "should be valid in nominal setup" do
|
||||
@override_student.should be_valid
|
||||
end
|
||||
|
||||
it "should reject an assignment other than that of the override" do
|
||||
@override_student.assignment = assignment_model
|
||||
@override_student.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject an empty assignment_override" do
|
||||
@override_student.assignment_override = nil
|
||||
@override_student.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject a non-adhoc assignment_override" do
|
||||
@override_student.assignment_override.set = @course.default_section
|
||||
@override_student.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject an empty user" do
|
||||
@override_student.user = nil
|
||||
@override_student.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject a student not in the course" do
|
||||
@override_student.user = user_model
|
||||
@override_student.should_not be_valid
|
||||
end
|
||||
|
||||
it "should reject duplicate tuples" do
|
||||
@override_student.save!
|
||||
@override_student2 = @override.assignment_override_students.build
|
||||
@override_student2.user = @student
|
||||
@override_student2.should_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
it "should maintain assignment from assignment_override" do
|
||||
student_in_course
|
||||
@override1 = assignment_override_model(:course => @course)
|
||||
@override2 = assignment_override_model(:course => @course)
|
||||
@override1.assignment_id.should_not == @override2.assignment_id
|
||||
|
||||
@override_student = @override1.assignment_override_students.build
|
||||
@override_student.user = @student
|
||||
@override_student.valid? # trigger maintenance
|
||||
@override_student.assignment_id.should == @override1.assignment_id
|
||||
@override_student.assignment_override = @override2
|
||||
@override_student.valid? # trigger maintenance
|
||||
@override_student.assignment_id.should == @override2.assignment_id
|
||||
end
|
||||
end
|
|
@ -417,18 +417,162 @@ describe Assignment do
|
|||
end
|
||||
end
|
||||
|
||||
it "should treat 11:59pm as an all_day" do
|
||||
assignment_model(:due_at => "Sep 4 2008 11:59pm")
|
||||
@assignment.all_day.should eql(true)
|
||||
@assignment.due_at.strftime("%H:%M").should eql("23:59")
|
||||
@assignment.all_day_date.should eql(Date.parse("Sep 4 2008"))
|
||||
describe "all_day and all_day_date from due_at" do
|
||||
def fancy_midnight(opts={})
|
||||
zone = opts[:zone] || Time.zone
|
||||
Time.use_zone(zone) do
|
||||
time = opts[:time] || Time.zone.now
|
||||
time.in_time_zone.midnight + 1.day - 1.minute
|
||||
end
|
||||
end
|
||||
|
||||
before :each do
|
||||
@assignment = assignment_model
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with no prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == true
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with same-tz all-day prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.day
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == true
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with other-tz all-day prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Baghdad')
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == true
|
||||
end
|
||||
|
||||
it "should interpret 11:59pm as all day with non-all-day prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == true
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day no prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
||||
@assignment.time_zone_edited = 'Baghdad'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == false
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day with same-tz all-day prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == false
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day with other-tz all-day prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Baghdad')
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == false
|
||||
end
|
||||
|
||||
it "should not interpret non-11:59pm as all day with non-all-day prior value" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 1.hour
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska') + 2.hour
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == false
|
||||
end
|
||||
|
||||
it "should preserve all-day when only changing time zone" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
||||
@assignment.time_zone_edited = 'Baghdad'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == true
|
||||
end
|
||||
|
||||
it "should preserve non-all-day when only changing time zone" do
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska').in_time_zone('Baghdad')
|
||||
@assignment.save!
|
||||
@assignment.due_at = fancy_midnight(:zone => 'Alaska')
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day.should == false
|
||||
end
|
||||
|
||||
it "should determine date from due_at's timezone" do
|
||||
@assignment.due_at = Date.today.in_time_zone('Baghdad') + 1.hour # 01:00:00 AST +03:00 today
|
||||
@assignment.time_zone_edited = 'Baghdad'
|
||||
@assignment.save!
|
||||
@assignment.all_day_date.should == Date.today
|
||||
|
||||
@assignment.due_at = @assignment.due_at.in_time_zone('Alaska') - 2.hours # 12:00:00 AKDT -08:00 previous day
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day_date.should == Date.today - 1.day
|
||||
end
|
||||
|
||||
it "should preserve all-day date when only changing time zone" do
|
||||
@assignment.due_at = Date.today.in_time_zone('Baghdad') # 00:00:00 AST +03:00 today
|
||||
@assignment.time_zone_edited = 'Baghdad'
|
||||
@assignment.save!
|
||||
@assignment.due_at = @assignment.due_at.in_time_zone('Alaska') # 13:00:00 AKDT -08:00 previous day
|
||||
@assignment.time_zone_edited = 'Alaska'
|
||||
@assignment.save!
|
||||
@assignment.all_day_date.should == Date.today
|
||||
end
|
||||
|
||||
it "should preserve non-all-day date when only changing time zone" do
|
||||
@assignment.due_at = Date.today.in_time_zone('Alaska') - 11.hours # 13:00:00 AKDT -08:00 previous day
|
||||
@assignment.save!
|
||||
@assignment.due_at = @assignment.due_at.in_time_zone('Baghdad') # 00:00:00 AST +03:00 today
|
||||
@assignment.time_zone_edited = 'Baghdad'
|
||||
@assignment.save!
|
||||
@assignment.all_day_date.should == Date.today - 1.day
|
||||
end
|
||||
end
|
||||
|
||||
it "should not be set to all_day if a time is specified" do
|
||||
assignment_model(:due_at => "Sep 4 2008 11:58pm")
|
||||
@assignment.all_day.should eql(false)
|
||||
@assignment.due_at.strftime("%H:%M").should eql("23:58")
|
||||
@assignment.all_day_date.should eql(Date.parse("Sep 4 2008"))
|
||||
it "should destroy group overrides when the group category changes" do
|
||||
@assignment = assignment_model
|
||||
@assignment.group_category = @assignment.context.group_categories.create!
|
||||
@assignment.save!
|
||||
|
||||
overrides = 5.times.map do
|
||||
override = @assignment.assignment_overrides.build
|
||||
override.set = @assignment.group_category.groups.create!
|
||||
override.save!
|
||||
|
||||
override.workflow_state.should == 'active'
|
||||
override
|
||||
end
|
||||
|
||||
@assignment.group_category = @assignment.context.group_categories.create!
|
||||
@assignment.save!
|
||||
|
||||
overrides.each do |override|
|
||||
override.reload
|
||||
|
||||
override.workflow_state.should == 'deleted'
|
||||
override.versions.size.should == 2
|
||||
override.assignment_version.should == @assignment.version_number
|
||||
end
|
||||
end
|
||||
|
||||
context "concurrent inserts" do
|
||||
|
|
Loading…
Reference in New Issue