canvas-lms/spec/controllers/discussion_topics_controlle...

670 lines
24 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')
describe DiscussionTopicsController do
before :once do
course_with_teacher(:active_all => true)
course_with_observer(:active_all => true, :course => @course)
@observer_enrollment = @enrollment
student_in_course(:active_all => true)
end
def course_topic(opts={})
@topic = @course.discussion_topics.build(:title => "some topic", :pinned => opts[:pinned])
user = opts[:user] || @user
if user && !opts[:skip_set_user]
@topic.user = user
end
if opts[:with_assignment]
@topic.assignment = @course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title)
@topic.assignment.infer_times
@topic.assignment.saved_by = :discussion_topic
end
@topic.save
@topic
end
def topic_entry
@entry = @topic.discussion_entries.create(:message => "some message", :user => @user)
end
describe "GET 'index'" do
it "should require authorization" do
get 'index', :course_id => @course.id
assert_unauthorized
end
it "should require the course to be published for students" do
@course.claim
user_session(@student)
get 'index', :course_id => @course.id
assert_unauthorized
end
it "should load for :view_group_pages students" do
@course.account.role_overrides.create!(
role: student_role,
permission: 'view_group_pages',
enabled: true
)
@group_category = @course.group_categories.create(:name => 'gc')
@group = @course.groups.create!(:group_category => @group_category)
user_session(@student)
get 'index', :group_id => @group.id
expect(response).to be_success
end
context "graded group discussion" do
before do
@course.account.role_overrides.create!(
role: student_role,
permission: 'view_group_pages',
enabled: true
)
@course.enable_feature!(:differentiated_assignments)
group_discussion_assignment
@child_topic = @topic.child_topics.first
@group = @child_topic.context
@group.add_user(@student)
@assignment.only_visible_to_overrides = true
@assignment.save!
end
it "should return graded and visible group discussions properly" do
cs = @student.enrollments.first.course_section
create_section_override_for_assignment(@assignment, {course_section: cs})
user_session(@student)
get 'index', :group_id => @group.id
expect(response).to be_success
expect(assigns["topics"]).to include(@child_topic)
end
it "should not return graded group discussions if a student has no visibility" do
user_session(@student)
get 'index', :group_id => @group.id
expect(response).to be_success
expect(assigns["topics"]).not_to include(@child_topic)
end
end
it "should return non-graded group discussions properly" do
@course.account.role_overrides.create!(
role: student_role,
permission: 'view_group_pages',
enabled: true
)
@course.enable_feature!(:differentiated_assignments)
group_category(context: @course)
membership = group_with_user(group_category: @group_category, user: @student, context: @course)
@topic = @group.discussion_topics.create(:title => "group topic")
@topic.context = @group
@topic.save!
user_session(@student)
get 'index', :group_id => @group.id
expect(response).to be_success
expect(assigns["topics"]).to include(@topic)
end
end
describe "GET 'show'" do
it "should require authorization" do
course_topic
get 'show', :course_id => @course.id, :id => @topic.id
assert_unauthorized
end
it "should require the course to be published for students" do
course_topic
@course.claim
user_session(@student)
get 'show', :course_id => @course.id, :id => @topic.id
assert_unauthorized
end
it "should work for announcements in a public course" do
@course.update_attribute(:is_public, true)
@announcement = @course.announcements.create!(
:title => "some announcement",
:message => "some message"
)
get 'show', :course_id => @course.id, :id => @announcement.id
expect(response).to be_success
end
it "should not display announcements in private courses to users who aren't logged in" do
announcement = @course.announcements.create!(title: 'Test announcement', message: 'Message')
get('show', course_id: @course.id, id: announcement.id)
assert_unauthorized
end
context "discussion topic with assignment with overrides" do
render_views
before :once do
course_topic(user: @teacher, with_assignment: true)
@section = @course.course_sections.create!(:name => "I <3 Discusions")
@override = assignment_override_model(:assignment => @topic.assignment,
:due_at => Time.now,
:set => @section)
end
it "doesn't show overrides to students" do
user_session(@student)
get 'show', :course_id => @course.id, :id => @topic.id
expect(response).to be_success
expect(response.body).not_to match 'discussion-topic-due-dates'
due_date = OverrideListPresenter.new.due_at(@topic.assignment)
expect(response.body).to match "due #{due_date}"
end
it "doesn't show overrides for observers" do
user_session(@observer)
get 'show', :course_id => @course.id, :id => @topic.id
expect(response).to be_success
expect(response.body).not_to match 'discussion-topic-due-dates'
due_date = OverrideListPresenter.new.due_at(@topic.assignment.overridden_for(@observer))
expect(response.body).to match "due #{due_date}"
end
it "does show overrides to teachers" do
user_session(@teacher)
get 'show', :course_id => @course.id, :id => @topic.id
expect(response).to be_success
expect(response.body).to match 'discussion-topic-due-dates'
end
end
it "should assign variables" do
user_session(@student)
course_topic
topic_entry
@topic.reload
expect(@topic.discussion_entries).not_to be_empty
get 'show', :course_id => @course.id, :id => @topic.id
expect(response).to be_success
expect(assigns[:topic]).not_to be_nil
expect(assigns[:topic]).to eql(@topic)
end
it "should display speedgrader when not for a large course" do
user_session(@teacher)
course_topic(user: @teacher, with_assignment: true)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:js_env][:DISCUSSION][:SPEEDGRADER_URL_TEMPLATE]).to be_truthy
end
it "should hide speedgrader when for a large course" do
user_session(@teacher)
course_topic(user: @teacher, with_assignment: true)
Course.any_instance.stubs(:large_roster?).returns(true)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:js_env][:DISCUSSION][:SPEEDGRADER_URL_TEMPLATE]).to be_nil
end
it "should setup speedgrader template for variable substitution" do
user_session(@teacher)
course_topic(user: @teacher, with_assignment: true)
get 'show', :course_id => @course.id, :id => @topic.id
# this is essentially a unit test for app/coffeescripts/models/Entry.coffee,
# making sure that we get back the expected format for this url template
template = assigns[:js_env][:DISCUSSION][:SPEEDGRADER_URL_TEMPLATE]
url = template.gsub(/%22:student_id%22/, '123')
expect(url).to match "%7B%22student_id%22:123%7D"
end
it "should mark as read when viewed" do
user_session(@student)
course_topic(:skip_set_user => true)
expect(@topic.read_state(@student)).to eq 'unread'
get 'show', :course_id => @course.id, :id => @topic.id
expect(@topic.reload.read_state(@student)).to eq 'read'
end
it "should not mark as read if locked" do
user_session(@student)
course_topic(:skip_set_user => true)
mod = @course.context_modules.create! name: 'no soup for you', unlock_at: 1.year.from_now
mod.add_item(type: 'discussion_topic', id: @topic.id)
mod.save!
expect(@topic.read_state(@student)).to eq 'unread'
get 'show', :course_id => @course.id, :id => @topic.id
expect(@topic.reload.read_state(@student)).to eq 'unread'
end
it "should allow concluded teachers to see discussions" do
user_session(@teacher)
course_topic
@enrollment.conclude
get 'show', :course_id => @course.id, :id => @topic.id
expect(response).to be_success
get 'index', :course_id => @course.id
expect(response).to be_success
end
it "should allow concluded students to see discussions" do
user_session(@student)
course_topic
@enrollment.conclude
get 'show', :course_id => @course.id, :id => @topic.id
expect(response).to be_success
get 'index', :course_id => @course.id
expect(response).to be_success
end
context 'group discussions' do
before(:once) do
@group_category = @course.group_categories.create(:name => 'category 1')
@group1 = @course.groups.create!(:group_category => @group_category)
@group2 = @course.groups.create!(:group_category => @group_category)
group_category2 = @course.group_categories.create(:name => 'category 2')
@course.groups.create!(:group_category => group_category2)
end
it "should assign groups from the topic's category" do
user_session(@teacher)
course_topic(user: @teacher, with_assignment: true)
@topic.group_category = @group_category
@topic.save!
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:groups].size).to eql(2)
end
it "should redirect to the student's group" do
user_session(@student)
@group1.add_user(@student)
course_topic(user: @teacher, with_assignment: true)
@topic.group_category = @group_category
@topic.save!
get 'show', :course_id => @course.id, :id => @topic.id
redirect_path = "/groups/#{@group1.id}/discussion_topics?root_discussion_topic_id=#{@topic.id}"
expect(response).to redirect_to redirect_path
end
it "should redirect to the student's group even if students can view all groups" do
@course.account.role_overrides.create!(
role: student_role,
permission: 'view_group_pages',
enabled: true
)
user_session(@student)
@group1.add_user(@student)
course_topic(user: @teacher, with_assignment: true)
@topic.group_category = @group_category
@topic.save!
get 'show', :course_id => @course.id, :id => @topic.id
redirect_path = "/groups/#{@group1.id}/discussion_topics?root_discussion_topic_id=#{@topic.id}"
expect(response).to redirect_to redirect_path
end
end
context 'publishing' do
render_views
it "hides the publish icon for announcements" do
user_session(@teacher)
@context = @course
@announcement = @course.announcements.create!(
:title => "some announcement",
:message => "some message"
)
get 'show', :course_id => @course.id, :id => @announcement.id
expect(response.body).not_to match "topic_publish_button"
end
end
context "posting first to view setting" do
before(:once) do
@observer_enrollment.associated_user = @student
@observer_enrollment.save
@observer.reload
@context = @course
discussion_topic_model
@topic.require_initial_post = true
@topic.save
end
it "should allow admins to see posts without posting" do
@topic.reply_from(:user => @student, :text => 'hai')
user_session(@teacher)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:initial_post_required]).to be_falsey
end
it "shouldn't allow student who hasn't posted to see" do
@topic.reply_from(:user => @teacher, :text => 'hai')
user_session(@student)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:initial_post_required]).to be_truthy
end
it "shouldn't allow student's observer who hasn't posted to see" do
@topic.reply_from(:user => @teacher, :text => 'hai')
user_session(@observer)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:initial_post_required]).to be_truthy
end
it "should allow student who has posted to see" do
@topic.reply_from(:user => @student, :text => 'hai')
user_session(@student)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:initial_post_required]).to be_falsey
end
it "should allow student's observer who has posted to see" do
@topic.reply_from(:user => @student, :text => 'hai')
user_session(@observer)
get 'show', :course_id => @course.id, :id => @topic.id
expect(assigns[:initial_post_required]).to be_falsey
end
end
end
describe "GET 'new'" do
it "should maintain date and time when passed params" do
user_session(@teacher)
due_at = 1.day.from_now
get 'new', course_id: @course.id, due_at: due_at.iso8601
expect(assigns[:js_env][:DISCUSSION_TOPIC][:ATTRIBUTES][:assignment][:due_at]).to eq due_at.iso8601
end
end
describe "GET 'public_feed.atom'" do
before(:once) do
course_topic
end
it "should require authorization" do
get 'public_feed', :format => 'atom', :feed_code => @course.feed_code + 'x'
expect(assigns[:problem]).to eql("The verification code is invalid.")
end
it "should include absolute path for rel='self' link" do
get 'public_feed', :format => 'atom', :feed_code => @course.feed_code
feed = Atom::Feed.load_feed(response.body) rescue nil
expect(feed).not_to be_nil
expect(feed.links.first.rel).to match(/self/)
expect(feed.links.first.href).to match(/http:\/\//)
end
it "should include an author for each entry" do
get 'public_feed', :format => 'atom', :feed_code => @course.feed_code
feed = Atom::Feed.load_feed(response.body) rescue nil
expect(feed).not_to be_nil
expect(feed.entries).not_to be_empty
expect(feed.entries.all?{|e| e.authors.present?}).to be_truthy
end
end
describe 'POST create:' do
before(:once) do
Setting.set('enable_page_views', 'db')
end
before(:each) do
controller.stubs(:form_authenticity_token => 'abc', :form_authenticity_param => 'abc')
end
def topic_params(course, opts={})
{
:course_id => course.id,
:title => 'Topic Title',
:is_announcement => false,
:discussion_type => 'side_comment',
:require_initial_post => true,
:podcast_has_student_posts => false,
:delayed_post_at => '',
:lock_at => '',
:message => 'Message',
:delay_posting => false,
:threaded => false
}.merge(opts)
end
def assignment_params(course, opts={})
course.require_assignment_group
{
assignment: {
points_possible: 1,
grading_type: 'points',
assignment_group_id: @course.assignment_groups.first.id,
}.merge(opts)
}
end
describe 'the new topic' do
let(:topic) { assigns[:topic] }
before(:each) do
user_session(@student)
post 'create', { :format => :json }.merge(topic_params(@course))
end
specify { expect(topic).to be_a DiscussionTopic }
specify { expect(topic.user).to eq @user }
specify { expect(topic.current_user).to eq @user }
specify { expect(topic.delayed_post_at).to be_nil }
specify { expect(topic.lock_at).to be_nil }
specify { expect(topic.workflow_state).to eq 'active' }
specify { expect(topic.id).not_to be_nil }
specify { expect(topic.title).to eq 'Topic Title' }
specify { expect(topic.is_announcement).to be_falsey }
specify { expect(topic.discussion_type).to eq 'side_comment' }
specify { expect(topic.message).to eq 'Message' }
specify { expect(topic.threaded).to be_falsey }
end
it 'logs an asset access record for the discussion topic' do
user_session(@student)
post 'create', { :format => :json }.merge(topic_params(@course))
accessed_asset = assigns[:accessed_asset]
expect(accessed_asset[:category]).to eq 'topics'
expect(accessed_asset[:level]).to eq 'participate'
end
it 'registers a page view' do
user_session(@student)
post 'create', { :format => :json }.merge(topic_params(@course))
page_view = assigns[:page_view]
expect(page_view).not_to be_nil
expect(page_view.http_method).to eq 'post'
expect(page_view.url).to match %r{^http://test\.host/api/v1/courses/\d+/discussion_topics}
expect(page_view.participated).to be_truthy
end
it 'does not dispatch assignment created notification for unpublished graded topics' do
notification = Notification.create(:name => "Assignment Created")
obj_params = topic_params(@course).merge(assignment_params(@course))
user_session(@teacher)
post 'create', { :format => :json }.merge(obj_params)
json = JSON.parse response.body
topic = DiscussionTopic.find(json['id'])
expect(topic).to be_unpublished
expect(topic.assignment).to be_unpublished
expect(@student.recent_stream_items.map {|item| item.data['notification_id']}).not_to include notification.id
end
it 'dispatches an assignment stream item with the correct title' do
notification = Notification.create(:name => "Assignment Created")
obj_params = topic_params(@course).
merge(assignment_params(@course)).
merge({published: true})
user_session(@teacher)
post 'create', { :format => :json }.merge(obj_params)
si = @student.recent_stream_items.detect do |item|
item.data['notification_id'] == notification.id
end
expect(si.data['subject']).to eq "Assignment Created - #{obj_params[:title]}, #{@course.name}"
end
it 'does not allow for anonymous peer review assignment' do
obj_params = topic_params(@course).merge(assignment_params(@course))
obj_params[:assignment][:anonymous_peer_reviews] = true
user_session(@teacher)
post 'create', { :format => :json }.merge(obj_params)
json = JSON.parse response.body
expect(json['assignment']['anonymous_peer_reviews']).to be_falsey
end
end
describe "PUT: update" do
before(:once) do
@topic = DiscussionTopic.create!(context: @course, title: 'Test Topic',
delayed_post_at: '2013-01-01T00:00:00UTC', lock_at: '2013-01-02T00:00:00UTC')
end
before(:each) do
user_session(@teacher)
end
it "should not clear lock_at if locked is not changed" do
put('update', course_id: @course.id, topic_id: @topic.id,
title: 'Updated Topic', format: 'json',
lock_at: @topic.lock_at, delayed_post_at: @topic.delayed_post_at,
locked: false)
expect(@topic.reload).not_to be_locked
expect(@topic.lock_at).not_to be_nil
end
it "should not clear delayed_post_at if published is not changed" do
@topic.workflow_state = 'post_delayed'
@topic.save!
put('update', course_id: @course.id, topic_id: @topic.id,
title: 'Updated Topic', format: 'json',
lock_at: @topic.lock_at, delayed_post_at: @topic.delayed_post_at,
published: false)
expect(@topic.reload).not_to be_published
expect(@topic.delayed_post_at).not_to be_nil
end
it "should unlock discussions with a lock_at attribute if lock state changes" do
@topic.lock!
put('update', course_id: @course.id, topic_id: @topic.id,
title: 'Updated Topic', format: 'json',
lock_at: @topic.lock_at, delayed_post_at: @topic.delayed_post_at,
locked: false)
expect(@topic.reload).not_to be_locked
expect(@topic.lock_at).to be_nil
end
it "should still update a topic if it is a group discussion (that has submission replies)" do
user_session(@teacher)
student_in_course
group_category = @course.group_categories.create(:name => 'category')
group = @course.groups.create!(:group_category => group_category)
group.add_user(@student)
course_topic(user: @teacher, with_assignment: true)
@topic.group_category = group_category
@topic.save!
@topic.publish!
subtopic = @topic.child_topic_for(@student)
subtopic.discussion_entries.create!(:message => "student message for grading", :user => @student)
subtopic.ensure_submission(@student)
subtopic.reply_from(:user => @student, :text => 'hai')
expect(subtopic.can_unpublish?).to eq false
put(:update, group_id: group.id, topic_id: subtopic.id,
title: 'Updated Topic', format: 'json', locked: true)
expect(response).to be_success
end
it "should set workflow to post_delayed when delayed_post_at and lock_at are in the future" do
put(:update, course_id: @course.id, topic_id: @topic.id,
title: 'Updated topic', format: 'json', delayed_post_at: Time.zone.now + 5.days)
expect(@topic.reload).to be_post_delayed
end
it "should not clear lock_at if lock state hasn't changed" do
put('update', course_id: @course.id, topic_id: @topic.id,
title: 'Updated Topic', format: 'json', lock_at: @topic.lock_at,
locked: true)
expect(@topic.reload).to be_locked
expect(@topic.lock_at).not_to be_nil
end
it "should set draft state on discussions with delayed_post_at" do
put('update', course_id: @course.id, topic_id: @topic.id,
title: 'Updated Topic', format: 'json',
lock_at: @topic.lock_at, delayed_post_at: @topic.delayed_post_at,
published: false)
expect(@topic.reload).not_to be_published
end
it "should delete attachments" do
attachment = @topic.attachment = attachment_model(context: @course)
@topic.lock_at = Time.now + 1.week
@topic.unlock_at = Time.now - 1.week
@topic.save!
@topic.unlock!
put('update', course_id: @course.id, topic_id: @topic.id,
format: 'json', remove_attachment: '1')
expect(response).to be_success
expect(@topic.reload.attachment).to be_nil
expect(attachment.reload).to be_deleted
end
end
describe "POST 'reorder'" do
it "should reorder pinned topics" do
user_session(@teacher)
# add noise
@course.announcements.create!(message: 'asdf')
course_topic
topics = 3.times.map { course_topic(pinned: true) }
expect(topics.map(&:position)).to eq [1, 2, 3]
t1, t2, _ = topics
post 'reorder', :course_id => @course.id, :order => "#{t2.id},#{t1.id}", :format => 'json'
expect(response).to be_success
topics.each &:reload
expect(topics.map(&:position)).to eq [2, 1, 3]
end
end
end