canvas-lms/spec/models/discussion_topic_spec.rb

808 lines
34 KiB
Ruby

#
# 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')
describe DiscussionTopic do
it "should santize message" do
course_model
@course.discussion_topics.create!(:message => "<a href='#' onclick='alert(12);'>only this should stay</a>")
@course.discussion_topics.first.message.should eql("<a href=\"#\">only this should stay</a>")
end
it "should default to side_comment type" do
d = DiscussionTopic.new
d.discussion_type.should == 'side_comment'
d.threaded = '1'
d.discussion_type.should == 'threaded'
d.threaded = ''
d.discussion_type.should == 'side_comment'
end
it "should require a valid discussion_type" do
@topic = course_model.discussion_topics.build(:message => 'test', :discussion_type => "gesundheit")
@topic.save.should == false
@topic.errors.detect { |e| e.first == 'discussion_type' }.should be_present
end
it "should update the assignment it is associated with" do
course_model
a = @course.assignments.create!(:title => "some assignment", :points_possible => 5)
a.points_possible.should eql(5.0)
a.submission_types.should_not eql("online_quiz")
t = @course.discussion_topics.build(:assignment => a, :title => "some topic", :message => "a little bit of content")
t.save
t.assignment_id.should eql(a.id)
t.assignment.should eql(a)
a.reload
a.discussion_topic.should eql(t)
a.submission_types.should eql("discussion_topic")
end
it "should delete the assignment if the topic is no longer graded" do
course_model
a = @course.assignments.create!(:title => "some assignment", :points_possible => 5)
a.points_possible.should eql(5.0)
a.submission_types.should_not eql("online_quiz")
t = @course.discussion_topics.build(:assignment => a, :title => "some topic", :message => "a little bit of content")
t.save
t.assignment_id.should eql(a.id)
t.assignment.should eql(a)
a.reload
a.discussion_topic.should eql(t)
t.assignment = nil
t.save
t.reload
t.assignment_id.should eql(nil)
t.assignment.should eql(nil)
a.reload
a.should be_deleted
end
it "should not grant permissions if it is locked" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@topic = @course.discussion_topics.create!(:user => @teacher)
relevant_permissions = [:read, :reply, :update, :delete]
(@topic.check_policy(@teacher) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply', 'update', 'delete'].sort
(@topic.check_policy(@student) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply'].sort
@topic.lock!
(@topic.check_policy(@teacher) & relevant_permissions).map(&:to_s).sort.should == ['read', 'update', 'delete'].sort
(@topic.check_policy(@student) & relevant_permissions).map(&:to_s).should == ['read']
@topic.unlock!
(@topic.check_policy(@teacher) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply', 'update', 'delete'].sort
(@topic.check_policy(@student) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply'].sort
@entry = @topic.discussion_entries.create!(:user => @teacher)
@entry.discussion_topic = @topic
(@entry.check_policy(@teacher) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply', 'update', 'delete'].sort
(@entry.check_policy(@student) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply'].sort
@topic.lock!
(@topic.check_policy(@teacher) & relevant_permissions).map(&:to_s).sort.should == ['read', 'update', 'delete'].sort
(@entry.check_policy(@student) & relevant_permissions).map(&:to_s).should == ['read']
@topic.unlock!
(@entry.check_policy(@teacher) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply', 'update', 'delete'].sort
(@entry.check_policy(@student) & relevant_permissions).map(&:to_s).sort.should == ['read', 'reply'].sort
end
it "should grant observers read permission by default" do
course_with_teacher(:active_all => true)
course_with_observer(:course => @course, :active_all => true)
relevant_permissions = [:read, :reply, :update, :delete]
@topic = @course.discussion_topics.create!(:user => @teacher)
(@topic.check_policy(@observer) & relevant_permissions).map(&:to_s).sort.should == ['read'].sort
@entry = @topic.discussion_entries.create!(:user => @teacher)
(@entry.check_policy(@observer) & relevant_permissions).map(&:to_s).sort.should == ['read'].sort
end
it "should not grant observers read permission when read_forum override is false" do
course_with_teacher(:active_all => true)
course_with_observer(:course => @course, :active_all => true)
RoleOverride.create!(:context => @course.account, :permission => 'read_forum',
:enrollment_type => "ObserverEnrollment", :enabled => false)
relevant_permissions = [:read, :reply, :update, :delete]
@topic = @course.discussion_topics.create!(:user => @teacher)
(@topic.check_policy(@observer) & relevant_permissions).map(&:to_s).should be_empty
@entry = @topic.discussion_entries.create!(:user => @teacher)
(@entry.check_policy(@observer) & relevant_permissions).map(&:to_s).should be_empty
end
context "delayed posting" do
def delayed_discussion_topic(opts = {})
@topic = @course.discussion_topics.build(opts)
@topic.workflow_state = 'post_delayed'
@topic.save!
@topic
end
it "shouldn't send to streams on creation or update if it's delayed" do
course_with_student(:active_all => true)
@user.register
topic = @course.discussion_topics.create!(:title => "this should not be delayed", :message => "content here")
StreamItem.find_by_item_asset_string(topic.asset_string).should_not be_nil
topic = delayed_discussion_topic(:title => "this should be delayed", :message => "content here", :delayed_post_at => Time.now + 1.day)
StreamItem.find_by_item_asset_string(topic.asset_string).should be_nil
topic.message = "content changed!"
topic.save
StreamItem.find_by_item_asset_string(topic.asset_string).should be_nil
end
it "should send to streams on update from delayed to active" do
course_with_student(:active_all => true)
@user.register
topic = delayed_discussion_topic(:title => "this should be delayed", :message => "content here", :delayed_post_at => Time.now + 1.day)
topic.workflow_state.should == 'post_delayed'
StreamItem.find_by_item_asset_string(topic.asset_string).should be_nil
topic.delayed_post_at = nil
topic.title = "this isn't delayed any more"
topic.workflow_state = 'active'
topic.save!
StreamItem.find_by_item_asset_string(topic.asset_string).should_not be_nil
end
end
context "clone_for" do
it "should clone to another context" do
course_model
topic = @course.discussion_topics.create!(:message => "<a href='#' onclick='alert(12);'>only this should stay</a>", :title => "some topic")
course
new_topic = topic.clone_for(@course)
new_topic.context.should eql(@course)
new_topic.context.should_not eql(topic.context)
new_topic.message.should eql(topic.message)
new_topic.title.should eql(topic.title)
end
end
context "sub-topics" do
it "should default subtopics_refreshed_at on save if a group assignment" do
course_with_student(:active_all => true)
group_category = @course.group_categories.create(:name => "category")
@group = @course.groups.create(:name => "group", :group_category => group_category)
@topic = @course.discussion_topics.create(:title => "topic")
@topic.subtopics_refreshed_at.should be_nil
@topic.assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title, :group_category => @group.group_category)
@topic.assignment.infer_due_at
@topic.assignment.saved_by = :discussion_topic
@topic.save
@topic.subtopics_refreshed_at.should_not be_nil
end
it "should not allow students to edit sub-topics" do
course_with_student(:active_all => true)
@first_user = @user
@second_user = user_model
@course.enroll_student(@second_user).accept
@parent_topic = @course.discussion_topics.create!(:title => "parent topic", :message => "msg")
@group = @course.groups.create!(:name => "course group")
@group.add_user(@first_user)
@group.add_user(@second_user)
@group_topic = @group.discussion_topics.create!(:title => "group topic", :message => "ok to be edited", :user => @first_user)
@sub_topic = @group.discussion_topics.build(:title => "sub topic", :message => "not ok to be edited", :user => @first_user)
@sub_topic.root_topic_id = @parent_topic.id
@sub_topic.save!
@group_topic.grants_right?(@second_user, nil, :update).should eql(false)
@sub_topic.grants_right?(@second_user, nil, :update).should eql(false)
end
end
context "refresh_subtopics" do
it "should be a no-op unless there's an assignment and it has a group_category" do
course_with_student(:active_all => true)
@topic = @course.discussion_topics.create(:title => "topic")
@topic.refresh_subtopics
@topic.reload.child_topics.should be_empty
@topic.assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title)
@topic.assignment.saved_by = :discussion_topic
@topic.save
@topic.refresh_subtopics
@topic.reload.child_topics.should be_empty
end
it "should create a topic per active group in the category otherwise" do
group_discussion_assignment
@topic.refresh_subtopics
subtopics = @topic.reload.child_topics
subtopics.should_not be_nil
subtopics.size.should == 2
subtopics.each { |t| t.root_topic.should == @topic }
@group1.reload.discussion_topics.should_not be_empty
@group2.reload.discussion_topics.should_not be_empty
end
it "should copy appropriate attributes from the parent topic to subtopics on updates to the parent" do
group_discussion_assignment
@topic.refresh_subtopics
subtopics = @topic.reload.child_topics
subtopics.each {|st| st.discussion_type.should == 'side_comment' }
@topic.discussion_type = 'threaded'
@topic.save!
subtopics.each {|st| st.reload.discussion_type.should == 'threaded' }
end
it "should not rename the assignment to match a subtopic" do
group_discussion_assignment
original_name = @assignment.title
@assignment.reload
@assignment.title.should == original_name
end
end
context "root_topic?" do
it "should be false if the topic has a root topic" do
# subtopic has the assignment and group_category, but has a root topic
course_with_student(:active_all => true)
group_category = @course.group_categories.create(:name => "category")
@parent_topic = @course.discussion_topics.create(:title => "parent topic")
@subtopic = @parent_topic.child_topics.build(:title => "subtopic")
@assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @subtopic.title, :group_category => group_category)
@assignment.infer_due_at
@assignment.saved_by = :discussion_topic
@subtopic.assignment = @assignment
@subtopic.save
@subtopic.should_not be_root_topic
end
it "should be false unless the topic has an assignment" do
# topic has no root topic, but also has no assignment
course_with_student(:active_all => true)
@topic = @course.discussion_topics.create(:title => "subtopic")
@topic.should_not be_root_topic
end
it "should be false unless the topic's assignment has a group_category" do
# topic has no root topic and has an assignment, but the assignment has no group_category
course_with_student(:active_all => true)
@topic = @course.discussion_topics.create(:title => "topic")
@assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title)
@assignment.infer_due_at
@assignment.saved_by = :discussion_topic
@topic.assignment = @assignment
@topic.save
@topic.should_not be_root_topic
end
it "should be true otherwise" do
# topic meets all criteria
course_with_student(:active_all => true)
group_category = @course.group_categories.create(:name => "category")
@topic = @course.discussion_topics.create(:title => "topic")
@assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title, :group_category => group_category)
@assignment.infer_due_at
@assignment.saved_by = :discussion_topic
@topic.assignment = @assignment
@topic.save
@topic.should be_root_topic
end
end
context "#discussion_subentry_count" do
it "returns the count of all active discussion_entries" do
@student = student_in_course(:active_all => true).user
@topic = @course.discussion_topics.create(:title => "topic")
@topic.reply_from(:user => @teacher, :text => "entry 1").destroy # no count
@topic.reply_from(:user => @teacher, :text => "entry 1") # 1
@entry = @topic.reply_from(:user => @teacher, :text => "entry 2") # 2
@entry.reply_from(:user => @student, :html => "reply 1") # 3
@entry.reply_from(:user => @student, :html => "reply 2") # 4
# expect
@topic.discussion_subentry_count.should == 4
end
end
context "for_assignment?/for_group_assignment?" do
it "should not be for_assignment?/for_group_assignment? unless it has an assignment" do
course_with_student(:active_all => true)
@topic = @course.discussion_topics.create(:title => "topic")
@topic.should_not be_for_assignment
@topic.should_not be_for_group_assignment
group_category = @course.group_categories.build(:name => "category")
@topic.assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title, :group_category => group_category)
@topic.assignment.infer_due_at
@topic.assignment.saved_by = :discussion_topic
@topic.save
@topic.should be_for_assignment
@topic.should be_for_group_assignment
end
it "should not be for_group_assignment? unless the assignment has a group_category" do
course_with_student(:active_all => true)
@topic = @course.discussion_topics.build(:title => "topic")
@assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title)
@assignment.infer_due_at
@assignment.saved_by = :discussion_topic
@topic.assignment = @assignment
@topic.save
@topic.should be_for_assignment
@topic.should_not be_for_group_assignment
@assignment.group_category = @course.group_categories.create(:name => "category")
@assignment.save
@topic.reload.should be_for_group_assignment
end
end
context "should_send_to_stream" do
it "should be true for non-assignment discussions" do
course_with_student(:active_all => true)
@topic = @course.discussion_topics.create(:title => "topic")
@topic.should_send_to_stream.should be_true
end
it "should be true for non-group discussion assignments" do
course_with_student(:active_all => true)
@topic = @course.discussion_topics.build(:title => "topic")
@assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title, :due_at => 1.day.from_now)
@assignment.saved_by = :discussion_topic
@topic.assignment = @assignment
@topic.save
@topic.should_send_to_stream.should be_true
end
it "should be true for the parent topic only in group discussion assignments, not the subtopics" do
course_with_student(:active_all => true)
group_category = @course.group_categories.create(:name => "category")
@parent_topic = @course.discussion_topics.create(:title => "parent topic")
@subtopic = @parent_topic.child_topics.build(:title => "subtopic")
@assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @subtopic.title, :group_category => group_category, :due_at => 1.day.from_now)
@assignment.saved_by = :discussion_topic
@subtopic.assignment = @assignment
@subtopic.save
@parent_topic.should_send_to_stream.should be_true
@subtopic.should_send_to_stream.should be_false
end
it "should not send stream items to students if course isn't published'" do
course
course_with_teacher(:course => @course, :active_all => true)
student_in_course(:course => @course, :active_all => true)
topic = @course.discussion_topics.create!(:title => "secret topic", :user => @teacher)
StreamItem.for_user(@student).count.should == 0
StreamItem.for_user(@teacher).count.should == 1
topic.discussion_entries.create!
StreamItem.for_user(@student).count.should == 0
StreamItem.for_user(@teacher).count.should == 1
end
end
context "posting first to view" do
before(:each) do
course_with_student(:active_all => true)
@observer = user(:active_all => true)
course_with_teacher(:course => @course, :active_all => true)
@context = @course
discussion_topic_model
@topic.require_initial_post = true
@topic.save
end
it "should allow admins to see posts without posting" do
@topic.user_can_see_posts?(@teacher).should == true
end
it "should only allow active admins to see posts without posting" do
@ta_enrollment = course_with_ta(:course => @course, :active_enrollment => true)
# TA should be able to see
@topic.user_can_see_posts?(@ta).should == true
# Remove user as TA and enroll as student, should not be able to see
@ta_enrollment.destroy
# enroll as a student.
course_with_student(:course => @course, :user => @ta, :active_enrollment => true)
@topic.reload
@topic.user_can_see_posts?(@ta).should == false
end
it "shouldn't allow student (and observer) who hasn't posted to see" do
@topic.user_can_see_posts?(@student).should == false
end
it "should allow student (and observer) who has posted to see" do
@topic.reply_from(:user => @student, :text => 'hai')
@topic.user_can_see_posts?(@student).should == true
end
end
context "posters" do
before :each do
@teacher = course_with_teacher(:active_all => true).user
@context = @course
discussion_topic_model(:user => @teacher)
end
it "should include the topic author" do
@topic.posters.should include(@teacher)
end
it "should include users that have posted entries" do
@student = student_in_course(:active_all => true).user
@topic.reply_from(:user => @student, :text => "entry")
@topic.posters.should include(@student)
end
it "should include users that have replies to entries" do
@entry = @topic.reply_from(:user => @teacher, :text => "entry")
@student = student_in_course(:active_all => true).user
@entry.reply_from(:user => @student, :html => "reply")
@topic.posters.should include(@student)
end
it "should dedupe users" do
@entry = @topic.reply_from(:user => @teacher, :text => "entry")
@student = student_in_course(:active_all => true).user
@entry.reply_from(:user => @student, :html => "reply 1")
@entry.reply_from(:user => @student, :html => "reply 2")
@topic.posters.should include(@teacher)
@topic.posters.should include(@student)
@topic.posters.size.should == 2
end
it "should not include topic author if she is no longer enrolled in the course" do
student_in_course(:active_all => true)
@topic2 = @course.discussion_topics.create!(:title => "student topic", :message => "I'm outta here", :user => @student)
@entry = @topic2.discussion_entries.create!(:message => "go away", :user => @teacher)
@topic2.posters.map(&:id).sort.should eql [@student.id, @teacher.id].sort
@student.enrollments.first.destroy
@topic2.posters.map(&:id).sort.should eql [@teacher.id].sort
end
end
context "submissions when graded" do
before :each do
@teacher = course_with_teacher(:active_all => true).user
@context = @course
discussion_topic_model(:user => @teacher)
end
def build_submitted_assignment
student_in_course(:active_all => true)
@assignment = @course.assignments.create!(:title => "some discussion assignment")
@assignment.submission_types = 'discussion_topic'
@assignment.save!
@topic.assignment_id = @assignment.id
@topic.save!
@entry1 = @topic.discussion_entries.create!(:message => "second message", :user => @user)
@entry1.created_at = 1.week.ago
@entry1.save!
@submission = @assignment.submissions.scoped(:conditions => {:user_id => @entry1.user_id}).first
end
it "should not re-flag graded discussion as needs grading if student make another comment" do
student_in_course(:name => 'student in course')
assignment = @course.assignments.create(:title => "discussion assignment", :points_possible => 20)
topic = @course.discussion_topics.create!(:title => 'discussion topic 1', :message => "this is a new discussion topic", :assignment => assignment)
topic.discussion_entries.create!(:message => "student message for grading", :user => @student)
submissions = Submission.find_all_by_user_id_and_assignment_id(@student.id, assignment.id)
submissions.count.should == 1
student_submission = submissions.first
assignment.grade_student(@student, {:grade => 9})
student_submission.reload
student_submission.workflow_state.should == 'graded'
topic.discussion_entries.create!(:message => "student message 2 for grading", :user => @student)
submissions = Submission.find_all_by_user_id_and_assignment_id(@student.id, assignment.id)
submissions.count.should == 1
student_submission = submissions.first
student_submission.workflow_state.should == 'graded'
end
it "should create submissions for existing entries when setting the assignment" do
@student = student_in_course(:active_all => true).user
@topic.reply_from(:user => @student, :text => "entry")
@student.reload
@student.submissions.should be_empty
@assignment = assignment_model(:course => @course)
@topic.assignment = @assignment
@topic.save
@student.reload
@student.submissions.size.should == 1
@student.submissions.first.submission_type.should == 'discussion_topic'
end
it "should have the correct submission date if submission has comment" do
student_in_course(:active_all => true)
@assignment = @course.assignments.create!(:title => "some discussion assignment")
@assignment.submission_types = 'discussion_topic'
@assignment.save!
@topic.assignment = @assignment
@topic.save
te = @course.enroll_teacher(user)
@submission = @assignment.find_or_create_submission(@student.id)
@submission_comment = @submission.add_comment(:author => te.user, :comment => "some comment")
@submission.created_at = 1.week.ago
@submission.save!
@submission.workflow_state.should == 'unsubmitted'
@submission.submitted_at.should be_nil
@entry = @topic.discussion_entries.create!(:message => "somne discussion message", :user => @student)
@submission.reload
@submission.workflow_state.should == 'submitted'
@submission.submitted_at.to_i.should >= @entry.created_at.to_i #this time may not be exact because it goes off of time.now in the submission
end
it "should fix submission date after deleting the oldest entry" do
build_submitted_assignment()
@entry2 = @topic.discussion_entries.create!(:message => "some message", :user => @user)
@entry2.created_at = 1.day.ago
@entry2.save!
@entry1.destroy
@topic.reload
@topic.discussion_entries.should_not be_empty
@topic.discussion_entries.active.should_not be_empty
@submission.reload
@submission.submitted_at.to_i.should == @entry2.created_at.to_i
@submission.workflow_state.should == 'submitted'
end
it "should mark submission as unsubmitted after deletion" do
build_submitted_assignment()
@entry1.destroy
@topic.reload
@topic.discussion_entries.should_not be_empty
@topic.discussion_entries.active.should be_empty
@submission.reload
@submission.workflow_state.should == 'unsubmitted'
@submission.submission_type.should == nil
@submission.submitted_at.should == nil
end
it "should have new submission date after deletion and re-submission" do
build_submitted_assignment()
@entry1.destroy
@topic.reload
@topic.discussion_entries.should_not be_empty
@topic.discussion_entries.active.should be_empty
@entry2 = @topic.discussion_entries.create!(:message => "some message", :user => @user)
@submission.reload
@submission.submitted_at.to_i.should >= @entry2.created_at.to_i #this time may not be exact because it goes off of time.now in the submission
@submission.workflow_state.should == 'submitted'
end
it "should not duplicate submissions for existing entries that already have submissions" do
@student = student_in_course(:active_all => true).user
@assignment = assignment_model(:course => @course)
@topic.assignment = @assignment
@topic.save
@topic.reload # to get the student in topic.assignment.context.students
@topic.reply_from(:user => @student, :text => "entry")
@student.reload
@student.submissions.size.should == 1
@existing_submission_id = @student.submissions.first.id
@topic.assignment = nil
@topic.save
@topic.reply_from(:user => @student, :text => "another entry")
@student.reload
@student.submissions.size.should == 1
@student.submissions.first.id.should == @existing_submission_id
@topic.assignment = @assignment
@topic.save
@student.reload
@student.submissions.size.should == 1
@student.submissions.first.id.should == @existing_submission_id
end
it "should not resubmit graded discussion submissions" do
@student = student_in_course(:active_all => true).user
@assignment = assignment_model(:course => @course)
@topic.assignment = @assignment
@topic.save!
@topic.reload
@topic.reply_from(:user => @student, :text => "entry")
@student.reload
@assignment.grade_student(@student, :grade => 1)
@submission = Submission.find(:first, :conditions => {:user_id => @student.id, :assignment_id => @assignment.id})
@submission.workflow_state.should == 'graded'
@topic.ensure_submission(@student)
@submission.reload.workflow_state.should == 'graded'
end
end
context "read/unread state" do
before(:each) do
course_with_teacher(:active_all => true)
student_in_course(:active_all => true)
@topic = @course.discussion_topics.create!(:title => "title", :message => "message", :user => @teacher)
end
it "should mark a topic you created as read" do
@topic.read?(@teacher).should be_true
@topic.unread_count(@teacher).should == 0
end
it "should be unread by default" do
@topic.read?(@student).should be_false
@topic.unread_count(@student).should == 0
end
it "should allow being marked unread" do
@topic.change_read_state("unread", @teacher)
@topic.reload
@topic.read?(@teacher).should be_false
@topic.unread_count(@teacher).should == 0
end
it "should allow being marked read" do
@topic.change_read_state("read", @student)
@topic.reload
@topic.read?(@student).should be_true
@topic.unread_count(@student).should == 0
end
it "should allow mark all as unread" do
@entry = @topic.discussion_entries.create!(:message => "Hello!", :user => @teacher)
@topic.change_all_read_state("unread", @teacher)
@topic.reload
@topic.read?(@student).should be_false
@entry.read?(@student).should be_false
@topic.unread_count(@student).should == 1
end
it "should allow mark all as read" do
@entry = @topic.discussion_entries.create!(:message => "Hello!", :user => @teacher)
@topic.change_all_read_state("read", @student)
@topic.reload
@topic.read?(@student).should be_true
@entry.read?(@student).should be_true
@topic.unread_count(@student).should == 0
end
it "should use unique_constaint_retry when updating read state" do
DiscussionTopic.expects(:unique_constraint_retry).once
@topic.change_read_state("read", @student)
end
it "should use unique_constaint_retry when updating all read state" do
DiscussionTopic.expects(:unique_constraint_retry).once
@topic.change_all_read_state("unread", @student)
end
it "should sync unread state with the stream item" do
@stream_item = StreamItem.for_item_asset_string(@topic.asset_string).first
@stream_item.stream_item_instances.detect{|sii| sii.user_id == @teacher.id}.should be_read
@stream_item.stream_item_instances.detect{|sii| sii.user_id == @student.id}.should be_unread
@topic.change_all_read_state("unread", @teacher)
@topic.change_all_read_state("read", @student)
@topic.reload
@stream_item = StreamItem.for_item_asset_string(@topic.asset_string).first
@stream_item.stream_item_instances.detect{|sii| sii.user_id == @teacher.id}.should be_unread
@stream_item.stream_item_instances.detect{|sii| sii.user_id == @student.id}.should be_read
end
end
context "materialized view" do
before do
topic_with_nested_replies
run_transaction_commit_callbacks
end
it "should return nil if the view has not been built yet, and schedule a job" do
DiscussionTopic::MaterializedView.for(@topic).destroy
@topic.materialized_view.should be_nil
@topic.materialized_view.should be_nil
Delayed::Job.strand_size("materialized_discussion:#{@topic.id}").should == 1
end
it "should return the materialized view if it's up to date" do
run_jobs
view = DiscussionTopic::MaterializedView.find_by_discussion_topic_id(@topic.id)
@topic.materialized_view.should == [view.json_structure, view.participants_array, view.entry_ids_array, "[]"]
end
it "should update the materialized view on new entry" do
run_jobs
Delayed::Job.strand_size("materialized_discussion:#{@topic.id}").should == 0
@topic.reply_from(:user => @user, :text => "ohai")
run_transaction_commit_callbacks
Delayed::Job.strand_size("materialized_discussion:#{@topic.id}").should == 1
end
it "should update the materialized view on edited entry" do
reply = @topic.reply_from(:user => @user, :text => "ohai")
run_jobs
Delayed::Job.strand_size("materialized_discussion:#{@topic.id}").should == 0
reply.update_attributes(:message => "i got that wrong before")
run_transaction_commit_callbacks
Delayed::Job.strand_size("materialized_discussion:#{@topic.id}").should == 1
end
it "should return empty data for a materialized view on a new (unsaved) topic" do
new_topic = DiscussionTopic.new(:context => @topic.context, :discussion_type => DiscussionTopic::DiscussionTypes::SIDE_COMMENT)
new_topic.should be_new_record
new_topic.materialized_view.should == [ "[]", [], [], "[]" ]
Delayed::Job.strand_size("materialized_discussion:#{new_topic.id}").should == 0
end
end
context "destroy" do
it "should destroy the assignment and associated child topics" do
group_discussion_assignment
@topic.destroy
@topic.reload.should be_deleted
@topic.child_topics.each{ |ct| ct.reload.should be_deleted }
@assignment.reload.should be_deleted
end
it "should not revive the assignment if updated when deleted" do
group_discussion_assignment
@topic.destroy
@assignment.reload.should be_deleted
@topic.touch
@assignment.reload.should be_deleted
end
end
describe "reply_from" do
it "should ignore responses in deleted account" do
account = Account.create!
@teacher = course_with_teacher(:active_all => true, :account => account).user
@context = @course
discussion_topic_model(:user => @teacher)
account.destroy
lambda { @topic.reply_from(:user => @teacher, :text => "entry") }.should raise_error(IncomingMessageProcessor::UnknownAddressError)
end
it "should prefer html to text" do
course_with_teacher
discussion_topic_model
msg = @topic.reply_from(:user => @teacher, :text => "text body", :html => "<p>html body</p>")
msg.should_not be_nil
msg.message.should == "<p>html body</p>"
end
it "should not allow replies to locked topics" do
course_with_teacher
discussion_topic_model
@topic.lock!
lambda { @topic.reply_from(:user => @teacher, :text => "reply") }.should raise_error(IncomingMessageProcessor::ReplyToLockedTopicError)
end
end
end