canvas-lms/spec/models/folder_spec.rb

379 lines
15 KiB
Ruby

#
# Copyright (C) 2011 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../sharding_spec_helper.rb')
describe Folder do
before(:once) do
course_factory
end
it "should create a new instance given valid attributes" do
folder_model
end
it "should infer its full name if it has a parent folder" do
f = Folder.root_folders(@course).first
expect(f.full_name).to eql("course files")
child = f.active_sub_folders.build(:name => "child")
child.context = @course
child.save!
expect(child.parent_folder).to eql(f)
expect(child.full_name).to eql("course files/child")
grandchild = child.sub_folders.build(:name => "grandchild")
grandchild.context = @course
grandchild.save!
expect(grandchild.full_name).to eql("course files/child/grandchild")
great_grandchild = grandchild.sub_folders.build(:name => "great_grandchild")
great_grandchild.context = @course
great_grandchild.save!
expect(great_grandchild.full_name).to eql("course files/child/grandchild/great_grandchild")
grandchild.parent_folder = f
grandchild.save!
grandchild.reload
expect(grandchild.full_name).to eql("course files/grandchild")
great_grandchild.reload
expect(great_grandchild.full_name).to eql("course files/grandchild/great_grandchild")
end
it "should trim trailing whitespaces from folder names" do
f = Folder.root_folders(@course).first
expect(f.full_name).to eql("course files")
child = f.active_sub_folders.build(:name => "space cadet ")
child.context = @course
child.save!
expect(child.parent_folder).to eql(f)
expect(child.full_name).to eql("course files/space cadet")
end
it "should add an iterator to duplicate folder names" do
f = Folder.root_folders(@course).first
expect(f.full_name).to eql("course files")
child = f.active_sub_folders.build(:name => "child")
child.context = @course
child.save!
expect(child.parent_folder).to eql(f)
expect(child.full_name).to eql("course files/child")
child2 = f.active_sub_folders.build(:name => "child")
child2.context = @course
child2.save!
expect(child2.parent_folder).to eql(f)
expect(child2.full_name).to eql("course files/child 2")
end
it "should allow the iterator to increase beyond 10 for duplicate folder names" do
f = Folder.root_folders(@course).first
expect(f.full_name).to eql("course files")
child = f.active_sub_folders.build(:name => "child")
child.context = @course
child.save!
expect(child.parent_folder).to eql(f)
expect(child.full_name).to eql("course files/child")
2.upto(11) do |i|
duplicate = f.active_sub_folders.build(:name => "child")
duplicate.context = @course
duplicate.save!
expect(duplicate.parent_folder).to eql(f)
expect(duplicate.full_name).to eql("course files/child #{i}")
end
end
it "should not allow recursive folder structures" do
f1 = @course.folders.create!(:name => "f1")
f2 = f1.sub_folders.create!(:name => "f2", :context => @course)
f3 = f2.sub_folders.create!(:name => "f3", :context => @course)
f1.parent_folder = f3
expect(f1.save).to eq false
expect(f1.errors.detect { |e| e.first.to_s == 'parent_folder_id' }).to be_present
end
it "should not allow root folders to have their names changed" do
f1 = Folder.root_folders(@course).first
f1.reload
f1.update_attributes(:name => "something")
expect(f1.save).to eq false
expect(f1.errors.detect { |e| e.first.to_s == 'name' }).to be_present
end
it "files without an explicit folder_id should be inferred" do
f = @course.folders.create!(:name => "unfiled")
a = f.active_file_attachments.build
a.context = @course
a.uploaded_data = default_uploaded_data
a.save!
nil_a = @course.attachments.new
nil_a.update_attributes(:uploaded_data => default_uploaded_data)
expect(nil_a.folder_id).not_to be_nil
expect(f.active_file_attachments).to be_include(a)
# f.active_file_attachments.should be_include(nil_a)
end
it "should assign unfiled files to the 'unfiled' folder" do
f = Folder.unfiled_folder(@course)
a = f.file_attachments.build
a.context = @course
a.uploaded_data = default_uploaded_data
a.save!
nil_a = @course.attachments.new
nil_a.update_attributes(:uploaded_data => default_uploaded_data)
expect(f.active_file_attachments).to be_include(a)
expect(f.active_file_attachments).to be_include(nil_a)
end
it "should not return files without a folder_id if it's not the 'unfiled' folder" do
f = @course.folders.create!(:name => "not_unfiled")
a = f.active_file_attachments.build
a.context = @course
a.uploaded_data = default_uploaded_data
a.save!
nil_a = @course.attachments.create!(:uploaded_data => default_uploaded_data)
expect(f.active_file_attachments).to be_include(a)
expect(f.active_file_attachments).not_to be_include(nil_a)
end
it "should implement the not_locked scope correctly" do
not_locked = [
Folder.root_folders(@course).first,
@course.folders.create!(:name => "not locked 1", :locked => false),
@course.folders.create!(:name => "not locked 2", :lock_at => 1.days.from_now),
@course.folders.create!(:name => "not locked 3", :lock_at => 2.days.ago, :unlock_at => 1.days.ago)
]
locked = [
@course.folders.create!(:name => "locked 1", :locked => true),
@course.folders.create!(:name => "locked 2", :lock_at => 1.days.ago),
@course.folders.create!(:name => "locked 3", :lock_at => 1.days.ago, :unlock_at => 1.days.from_now)
]
expect(@course.folders.map(&:id).sort).to eq (not_locked + locked).map(&:id).sort
expect(@course.folders.not_locked.map(&:id).sort).to eq (not_locked).map(&:id).sort
end
it "should not create multiple root folders for a course" do
skip('spec requires postgres index') unless Folder.connection.adapter_name == 'PostgreSQL'
@course.folders.create!(:name => Folder::ROOT_FOLDER_NAME, :full_name => Folder::ROOT_FOLDER_NAME, :workflow_state => 'visible')
expect { @course.folders.create!(:name => Folder::ROOT_FOLDER_NAME, :full_name => Folder::ROOT_FOLDER_NAME, :workflow_state => 'visible') }.to raise_error(ActiveRecord::RecordNotUnique)
@course.reload
expect(@course.folders.count).to eq 1
end
describe ".assert_path" do
specs_require_sharding
it "should not get confused by the same context on multiple shards" do
user1 = User.create!
f1 = Folder.assert_path('myfolder', user1)
@shard1.activate do
user2 = User.new
user2.id = user1.local_id
user2.save!
f2 = Folder.assert_path('myfolder', user2)
expect(f2).not_to eq f1
end
end
end
describe "resolve_path" do
before :once do
@root_folder = Folder.root_folders(@course).first
end
it "should return a sequence of Folders" do
foo = @course.folders.create! name: 'foo', parent_folder: @root_folder
bar = @course.folders.create! name: 'bar', parent_folder: foo
expect(Folder.resolve_path(@course, "foo/bar")).to eql [@root_folder, foo, bar]
end
it "should ignore trailing slashes" do
foo = @course.folders.create! name: 'foo', parent_folder: @root_folder
expect(Folder.resolve_path(@course, "foo/")).to eql [@root_folder, foo]
end
it "should find the root folder given an empty path" do
expect(Folder.resolve_path(@course, '')).to eql [@root_folder]
end
it "should find the root folder given '/'" do
expect(Folder.resolve_path(@course, '/')).to eql [@root_folder]
end
it "should find the root folder given a nil path" do
expect(Folder.resolve_path(@course, nil)).to eql [@root_folder]
end
it "should find the root folder given an empty array" do
expect(Folder.resolve_path(@course, [])).to eql [@root_folder]
end
it "should return nil on incomplete match" do
foo = @course.folders.create! name: 'foo', parent_folder: @root_folder
expect(Folder.resolve_path(@course, "foo/bar")).to be_nil
end
it "should exclude hidden if specified" do
foo = @course.folders.create! name: 'foo', parent_folder: @root_folder
foo.update_attribute(:workflow_state, 'hidden')
bar = @course.folders.create! name: 'bar', parent_folder: foo
expect(Folder.resolve_path(@course, "foo/bar", true)).to eql [@root_folder, foo, bar]
expect(Folder.resolve_path(@course, "foo/bar", false)).to be_nil
end
it "should exclude locked if specified" do
foo = @course.folders.create! name: 'foo', parent_folder: @root_folder, locked: true
bar = @course.folders.create! name: 'bar', parent_folder: foo
expect(Folder.resolve_path(@course, "foo/bar", true)).to eql [@root_folder, foo, bar]
expect(Folder.resolve_path(@course, "foo/bar", false)).to be_nil
end
it "should accept an array" do
foo = @course.folders.create! name: 'foo', parent_folder: @root_folder
bar = @course.folders.create! name: 'bar', parent_folder: foo
expect(Folder.resolve_path(@course, ['foo', 'bar'])).to eql [@root_folder, foo, bar]
end
end
describe "file_attachments_visible_to" do
before(:once) do
@root_folder = Folder.root_folders(@course).first
attachment_model context: @course, display_name: 'normal.txt', folder: @root_folder, uploaded_data: default_uploaded_data
attachment_model context: @course, display_name: 'hidden.txt', folder: @root_folder, uploaded_data: default_uploaded_data, hidden: true
attachment_model context: @course, display_name: 'locked.txt', folder: @root_folder, uploaded_data: default_uploaded_data, locked: true
attachment_model context: @course, display_name: 'date_restricted_unlocked.txt', folder: @root_folder, uploaded_data: default_uploaded_data, unlock_at: 1.day.ago, lock_at: 1.year.from_now
attachment_model context: @course, display_name: 'date_restricted_locked.txt', folder: @root_folder, uploaded_data: default_uploaded_data, lock_at: 1.day.ago, unlock_at: 1.year.from_now
end
it "should include all files for teachers" do
teacher_in_course active_all: true
expect(@root_folder.file_attachments_visible_to(@teacher).map(&:name)).to match_array %w(normal.txt hidden.txt locked.txt date_restricted_unlocked.txt date_restricted_locked.txt)
end
it "should exclude locked and hidden files for students" do
student_in_course active_all: true
expect(@root_folder.file_attachments_visible_to(@student).map(&:name)).to match_array %w(normal.txt date_restricted_unlocked.txt)
end
end
describe "all_visible_folder_ids" do
before(:once) do
@root_folder = Folder.root_folders(@course).first
@normal_folder = @root_folder.active_sub_folders.create!(:context => @course, :name => "normal")
@normal_sub1 = @normal_folder.active_sub_folders.create!(:context => @course, :name => "normal_sub1")
@normal_sub2 = @normal_sub1.active_sub_folders.create!(:context => @course, :name => "normal_sub2")
@locked_folder = @root_folder.active_sub_folders.create!(:context => @course, :name => "locked", :lock_at => 1.week.ago)
@locked_sub1 = @locked_folder.active_sub_folders.create!(:context => @course, :name => "locked_sub1")
@locked_sub2 = @locked_sub1.active_sub_folders.create!(:context => @course, :name => "locked_sub2")
end
it "should exclude all descendants of locked folders" do
expect(Folder.all_visible_folder_ids(@course)).to match_array([@root_folder, @normal_folder, @normal_sub1, @normal_sub2].map(&:id))
end
end
describe "read_contents permission" do
before(:once) do
@course.offer!
@root_folder = Folder.root_folders(@course).first
student_in_course(:course => @course, :active_all => true)
teacher_in_course(:course => @course, :active_all => true)
end
it "should grant right to students and teachers" do
expect(@root_folder.grants_right?(@student, :read_contents)).to be_truthy
expect(@root_folder.grants_right?(@teacher, :read_contents)).to be_truthy
end
context "with files tab hidden to students" do
before :once do
@course.tab_configuration = [{"id" => Course::TAB_FILES, "hidden" => true}]
@course.save!
@root_folder.reload
end
it "should grant right to teachers but not students" do
expect(@root_folder.grants_right?(@student, :read_contents)).to be_falsey
expect(@root_folder.grants_right?(@teacher, :read_contents)).to be_truthy
end
it "should still grant rights to teachers even if the teacher enrollment is concluded" do
@teacher.enrollments.where(:course_id => @course).first.complete!
expect(@course.grants_right?(@teacher, :manage_files)).to be_falsey
expect(@root_folder.grants_right?(@teacher, :read_contents)).to be_truthy
end
end
end
describe ".from_context_or_id" do
it "delegate to root_folders when context is provided" do
folder = Folder.root_folders(@course).first
expect(Folder.from_context_or_id(@course, nil)).to eq(folder)
end
it "should find by id when context is not provided and id is" do
Folder.root_folders(@course).first
account_model
folder_model(context: @account)
expect(Folder.root_folders(@course).first).not_to eq(@folder),
'precondition'
expect(Folder.from_context_or_id(nil, @folder.id)).to eq(@folder)
end
it "should raise ActiveRecord::RecordNotFound when no record is found" do
expect(Folder.where(id: 1)).to be_empty, 'precondition'
expect {
Folder.from_context_or_id(nil, 1)
}.to raise_error(ActiveRecord::RecordNotFound)
end
end
describe "submissions folders" do
it "restricts valid contexts for submissions folders" do
student_in_course
group_model(:context => @course)
sf = Folder.new(name: 'test')
sf.submission_context_code = 'root'
sf.context = @user
expect(sf).to be_valid
sf.context = @group
expect(sf).to be_valid
sf.context = @course
expect(sf).not_to be_valid
end
it "ensures only one root submissions folder per user exists" do
user_factory
@user.submissions_folder
dup = @user.folders.build(name: 'dup', parent_folder: Folder.root_folders(@user).first)
dup.submission_context_code = 'root'
expect { dup.save! }.to raise_exception(ActiveRecord::RecordNotUnique)
end
it "ensures only one course submissions subfolder exists" do
student_in_course
@user.submissions_folder(@course)
dup = @user.folders.build(name: 'dup', parent_folder: @user.submissions_folder)
dup.submission_context_code = @course.asset_string
expect { dup.save! }.to raise_exception(ActiveRecord::RecordNotUnique)
end
end
end