canvas-lms/spec/spec_helper.rb

1256 lines
40 KiB
Ruby
Raw Normal View History

2011-02-01 09:57:29 +08:00
#
# 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/>.
#
begin; require File.expand_path(File.dirname(__FILE__) + "/../parallelized_specs/lib/parallelized_specs.rb"); rescue LoadError; end
ENV["RAILS_ENV"] = 'test'
require File.expand_path('../../config/environment', __FILE__) unless defined?(Rails)
if CANVAS_RAILS2
require 'spec'
# require 'spec/autorun'
require 'spec/rails'
# integration specs were renamed to request specs in rspec 2
def describe_with_rspec2_types(*args, &block)
unless args.last.is_a?(Hash)
args << {}
end
if args.last[:type] == :request
args.last[:type] = :integration
end
args.last[:location] ||= caller(0)[1]
describe_without_rspec2_types(*args, &block)
end
alias :describe_without_rspec2_types :describe
alias :describe :describe_with_rspec2_types
else
require 'rspec/rails'
end
2011-02-01 09:57:29 +08:00
require 'webrat'
require 'mocha/api'
require 'action_controller_test_process'
require File.expand_path(File.dirname(__FILE__) + '/mocha_rspec_adapter')
require File.expand_path(File.dirname(__FILE__) + '/mocha_extensions')
require File.expand_path(File.dirname(__FILE__) + '/ams_spec_helper')
2011-02-01 09:57:29 +08:00
Dir.glob("#{File.dirname(__FILE__).gsub(/\\/, "/")}/factories/*.rb").each { |file| require file }
def pend_with_bullet
if Bullet.enable?
pending ('PENDING: Bullet')
end
end
def require_webmock
# pull in webmock for selected tests, but leave it disabled by default.
# funky require order is to skip typhoeus because of an incompatibility
# see: https://github.com/typhoeus/typhoeus/issues/196
require 'webmock/util/version_checker'
require 'webmock/http_lib_adapters/http_lib_adapter_registry'
require 'webmock/http_lib_adapters/http_lib_adapter'
require 'webmock/http_lib_adapters/typhoeus_hydra_adapter'
WebMock::HttpLibAdapterRegistry.instance.http_lib_adapters.delete :typhoeus
require 'webmock/rspec'
end
# rspec aliases :describe to :context in a way that it's pretty much defined
# globally on every object. :context is already heavily used in our application,
# so we remove rspec's definition. This does not prevent 'context' from being
# used within a 'describe' block.
if defined?(Spec::DSL::Main)
module Spec::DSL::Main
remove_method :context if respond_to? :context
end
end
def truncate_table(model)
case model.connection.adapter_name
when "SQLite"
model.delete_all
begin
model.connection.execute("delete from sqlite_sequence where name='#{model.connection.quote_table_name(model.table_name)}';")
model.connection.execute("insert into sqlite_sequence (name, seq) values ('#{model.connection.quote_table_name(model.table_name)}', #{rand(100)});")
rescue
end
when "PostgreSQL"
begin
old_proc = model.connection.raw_connection.set_notice_processor {}
model.connection.execute("TRUNCATE TABLE #{model.connection.quote_table_name(model.table_name)} CASCADE")
ensure
model.connection.raw_connection.set_notice_processor(&old_proc)
end
else
model.connection.execute("SET FOREIGN_KEY_CHECKS=0")
model.connection.execute("TRUNCATE TABLE #{model.connection.quote_table_name(model.table_name)}")
model.connection.execute("SET FOREIGN_KEY_CHECKS=1")
end
end
def truncate_all_tables
models_by_connection = ActiveRecord::Base.all_models.group_by { |m| m.connection }
models_by_connection.each do |connection, models|
if connection.adapter_name == "PostgreSQL"
table_names = connection.tables & models.map(&:table_name)
connection.execute("TRUNCATE TABLE #{table_names.map { |t| connection.quote_table_name(t) }.join(',')}")
else
models.each { |model| truncate_table(model) }
end
end
end
def truncate_all_cassandra_tables
Canvas::Cassandra::DatabaseBuilder.config_names.each do |cass_config|
db = Canvas::Cassandra::DatabaseBuilder.from_config(cass_config)
db.tables.each do |table|
db.execute("TRUNCATE #{table}")
end
end
end
# wipe out the test db, in case some non-transactional tests crapped out before
# cleaning up after themselves
truncate_all_tables
# Make AR not puke if MySQL auto-commits the transaction
class ActiveRecord::ConnectionAdapters::MysqlAdapter < ActiveRecord::ConnectionAdapters::AbstractAdapter
def outside_transaction?
# MySQL ignores creation of savepoints outside of a transaction; so if we can create one
# and then can't release it because it doesn't exist, we're not in a transaction
execute('SAVEPOINT outside_transaction')
!!execute('RELEASE SAVEPOINT outside_transaction') rescue true
end
end
# Be sure to actually test serializing things to non-existent caches,
# but give Mocks a pass, since they won't exist in dev/prod
Mocha::Mock.class_eval do
def marshal_dump
nil
end
def marshal_load(data)
raise "Mocks aren't really serializeable!"
end
def respond_to_with_marshalling?(symbol, include_private = false)
return true if [:marshal_dump, :marshal_load].include?(symbol)
respond_to_without_marshalling?(symbol, include_private)
end
alias_method_chain :respond_to?, :marshalling
end
[ActiveSupport::Cache::MemoryStore, (CANVAS_RAILS2 ? NilStore : ActiveSupport::Cache::NullStore)].each do |store|
store.class_eval do
def write_with_serialization_check(name, value, options = nil)
Marshal.dump(value)
write_without_serialization_check(name, value, options)
end
alias_method_chain :write, :serialization_check
end
end
unless CANVAS_RAILS2
ActiveSupport::Cache::NullStore.class_eval do
def fetch_with_serialization_check(name, options = {}, &block)
result = fetch_without_serialization_check(name, options, &block)
Marshal.dump(result) if result
result
end
alias_method_chain :fetch, :serialization_check
end
end
matchers_module = (CANVAS_RAILS2 ? Spec::Matchers : RSpec::Matchers)
matchers_module.define :encompass do |expected|
match do |actual|
if expected.is_a?(Array) && actual.is_a?(Array)
expected.size == actual.size && expected.zip(actual).all? { |e, a| a.slice(*e.keys) == e }
elsif expected.is_a?(Hash) && actual.is_a?(Hash)
actual.slice(*expected.keys) == expected
else
false
end
end
end
matchers_module.define :match_ignoring_whitespace do |expected|
def whitespaceless(str)
str.gsub(/\s+/, '')
end
match do |actual|
whitespaceless(actual) == whitespaceless(expected)
end
end
(CANVAS_RAILS2 ? Spec::Runner : RSpec).configure do |config|
2011-02-01 09:57:29 +08:00
# If you're not using ActiveRecord you should remove these
# lines, delete config/database.yml and disable :active_record
# in your config/boot.rb
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = Rails.root+'spec/fixtures/'
2011-02-01 09:57:29 +08:00
config.include Webrat::Matchers, :type => :views
2011-02-01 09:57:29 +08:00
config.before :all do
# so before(:all)'s don't get confused
Account.clear_special_account_cache!
Notification.after_create { Notification.reset_cache! }
end
def delete_fixtures!
# noop for now, needed for plugin spec tweaks. implementation coming
# in g/24755
end
config.before :each do
I18n.locale = :en
Time.zone = 'UTC'
Account.clear_special_account_cache!
Account.default.update_attribute(:default_time_zone, 'UTC')
Setting.reset_cache!
HostUrl.reset_cache!
Notification.reset_cache!
ActiveRecord::Base.reset_any_instantiation!
Attachment.clear_cached_mime_ids
RoleOverride.clear_cached_contexts
Delayed::Job.redis.flushdb if Delayed::Job == Delayed::Backend::Redis::Job
truncate_all_cassandra_tables
Rails::logger.try(:info, "Running #{self.class.description} #{@method_name}")
Attachment.domain_namespace = nil
end
# flush redis before the first spec, and before each spec that comes after
# one that used redis
class << Canvas
attr_accessor :redis_used
def redis_with_track_usage(*a, &b)
self.redis_used = true
redis_without_track_usage(*a, &b)
end
alias_method_chain :redis, :track_usage
Canvas.redis_used = true
end
config.before :each do
if Canvas.redis_enabled? && Canvas.redis_used
Canvas.redis.flushdb rescue nil
end
Canvas.redis_used = false
end
def account_with_cas(opts={})
@account = opts[:account]
@account ||= Account.create!
config = AccountAuthorizationConfig.new
cas_url = opts[:cas_url] || "https://localhost/cas"
config.auth_type = "cas"
config.auth_base = cas_url
config.log_in_url = opts[:cas_log_in_url] if opts[:cas_log_in_url]
@account.account_authorization_configs << config
@account
end
def account_with_saml(opts={})
@account = opts[:account]
@account ||= Account.create!
config = AccountAuthorizationConfig.new
config.auth_type = "saml"
config.log_in_url = opts[:saml_log_in_url] if opts[:saml_log_in_url]
@account.account_authorization_configs << config
@account
end
2011-02-01 09:57:29 +08:00
def course(opts={})
account = opts[:account] || Account.default
account.shard.activate do
@course = Course.create!(:name => opts[:course_name], :account => account)
@course.offer! if opts[:active_course] || opts[:active_all]
if opts[:active_all]
u = User.create!
u.register!
e = @course.enroll_teacher(u)
e.workflow_state = 'active'
e.save!
@teacher = u
end
remove hide_from_students from the new pages UI test plan: - start with draft state disabled - for each step, exercise the api (as a teacher) to confirm the expected values 1) create pages with different hide_from_students values a) as a teacher hidden pages should be visible; the hidden flag should persist b) as a student hidden pages should not be visible 2) using the api, update published and hide_from_students (together and separately) a) the published value should not be affected b) the hide_from_students value should be updated when set 3) enable draft state a) as a teacher previously hidden pages should be visible, but unpublished b) as a student previously hidden pages should not be visible 4) edit a page a) ensure the "Hide from students" option is gone 5) publishing pages should behave as expected a) published pages should be visible to students b) unpublished pages should not be visible to students 6) using the api, update published and hide_from_students (together and separately) a) the hide_from_students value should not be affected b) the published value should be updated when set 7) create new pages with different published states a) as a teacher unpublished pages should be visible, but unpublished b) as a student unpublished pages should not be visible 8) disable draft state a) as a teacher previously unpublished pages should be marked hidden b) as a student previously unpublished pages should not be visible fixes CNVS-7617 Change-Id: I395e0b2639543a64d9e2bc8d9377c78cf36f42d6 Reviewed-on: https://gerrit.instructure.com/23618 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: August Thornton <august@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: Bracken Mosbacker <bracken@instructure.com>
2013-08-23 04:04:59 +08:00
if opts[:draft_state]
account.allow_feature!(:draft_state)
@course.enable_feature!(:draft_state)
remove hide_from_students from the new pages UI test plan: - start with draft state disabled - for each step, exercise the api (as a teacher) to confirm the expected values 1) create pages with different hide_from_students values a) as a teacher hidden pages should be visible; the hidden flag should persist b) as a student hidden pages should not be visible 2) using the api, update published and hide_from_students (together and separately) a) the published value should not be affected b) the hide_from_students value should be updated when set 3) enable draft state a) as a teacher previously hidden pages should be visible, but unpublished b) as a student previously hidden pages should not be visible 4) edit a page a) ensure the "Hide from students" option is gone 5) publishing pages should behave as expected a) published pages should be visible to students b) unpublished pages should not be visible to students 6) using the api, update published and hide_from_students (together and separately) a) the hide_from_students value should not be affected b) the published value should be updated when set 7) create new pages with different published states a) as a teacher unpublished pages should be visible, but unpublished b) as a student unpublished pages should not be visible 8) disable draft state a) as a teacher previously unpublished pages should be marked hidden b) as a student previously unpublished pages should not be visible fixes CNVS-7617 Change-Id: I395e0b2639543a64d9e2bc8d9377c78cf36f42d6 Reviewed-on: https://gerrit.instructure.com/23618 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: August Thornton <august@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: Bracken Mosbacker <bracken@instructure.com>
2013-08-23 04:04:59 +08:00
end
2011-02-01 09:57:29 +08:00
end
@course
end
def account_admin_user_with_role_changes(opts={})
account = opts[:account] || Account.default
if opts[:role_changes]
opts[:role_changes].each_pair do |permission, enabled|
account.role_overrides.create(:permission => permission.to_s, :enrollment_type => opts[:membership_type] || 'AccountAdmin', :enabled => enabled)
end
end
RoleOverride.clear_cached_contexts
account_admin_user(opts)
end
def account_admin_user(opts={:active_user => true})
account = opts[:account] || Account.default
@user = opts[:user] || account.shard.activate { user(opts) }
@admin = @user
account_user = @user.account_users.build(:account => account, :membership_type => opts[:membership_type] || 'AccountAdmin')
account_user.shard = account.shard
account_user.save!
@user
end
def site_admin_user(opts={})
@user = opts[:user] || user(opts)
@admin = @user
Account.site_admin.add_user(@user, opts[:membership_type] || 'AccountAdmin')
@user
end
2011-02-01 09:57:29 +08:00
def user(opts={})
@user = User.create!(opts.slice(:name, :short_name))
if opts[:active_user] || opts[:active_all]
@user.accept_terms
@user.register!
end
@user.update_attribute :workflow_state, opts[:user_state] if opts[:user_state]
2011-02-01 09:57:29 +08:00
@user
end
def user_with_pseudonym(opts={})
user(opts) unless opts[:user]
user = opts[:user] || @user
@pseudonym = pseudonym(user, opts)
user
end
def communication_channel(user, opts={})
username = opts[:username] || "nobody@example.com"
@cc = user.communication_channels.create!(:path_type => 'email', :path => username) do |cc|
refactor user creation/invitations closes #5833 fixes #5573, #5572, #5753 * communication channels are now only unique within a single user * UserList changes * Always resolve pseudonym#unique_ids * Support looking up by SMS CCs * Option to either require e-mails match an existing CC, or e-mails that don't match a Pseudonym will always be returned unattached (relying on better merging behavior to not have a gazillion accounts created) * Method to return users, creating new ones (*without* a Pseudonym) if necessary. (can't create with a pseudonym, since Pseudonym#unique_id is still unique, I can't have multiple outstanding users with the same unique_id) * EnrollmentsFromUserList is mostly gutted, now using UserList's functionality directy. * Use UserList for adding account admins, removing the now unused Account#add_admin => User#find_by_email/User#assert_by_email codepath * Update UsersController#create to not worry about duplicate communication channels * Remove AccountsController#add_user, and just use UsersController#create * Change SIS::UserImporter to send out a merge opportunity e-mail if a conflicting CC is found (but still create the CC) * In /profile, don't worry about conflicting CCs (the CC confirmation process will now allow merging) * Remove CommunicationChannelsController#try_merge and #merge * For the non-simple case of CoursesController#enrollment_invitation redirect to /register (CommunicationsChannelController#confirm) * Remove CoursesController#transfer_enrollment * Move PseudonymsController#registration_confirmation to CommunicationChannelsController#confirm (have to be able to register an account without a Pseudonym yet) * Fold the old direct confirm functionality in, if there are no available merge opportunities * Allow merging the new account with the currently logged in user * Allow changing the Pseudonym#unique_id when registering a new account (since there might be conflicts) * Display a list of merge opportunities based on conflicting communication channels * Provide link(s) to log in as the other user, redirecting back to the registration page after login is complete (to complete the merge as the current user) * Remove several assert_* methods that are no longer needed * Update PseudonymSessionsController a bit to deal with the new way of dealing with conflicting CCs (especially CCs from LDAP), and to redirect back to the registration/confirmation page when attempting to do a merge * Expose the open_registration setting; use it to control if inviting users to a course is able to create new users Change-Id: If2f38818a71af656854d3bf8431ddbf5dcb84691 Reviewed-on: https://gerrit.instructure.com/6149 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Jacob Fugal <jacob@instructure.com>
2011-10-13 04:30:48 +08:00
cc.workflow_state = 'active' if opts[:active_cc] || opts[:active_all]
cc.workflow_state = opts[:cc_state] if opts[:cc_state]
end
2011-02-01 09:57:29 +08:00
@cc.should_not be_nil
@cc.should_not be_new_record
@cc
end
def user_with_communication_channel(opts={})
user(opts) unless opts[:user]
user = opts[:user] || @user
@cc = communication_channel(user, opts)
user
end
def pseudonym(user, opts={})
@spec_pseudonym_count ||= 0
username = opts[:username] || (@spec_pseudonym_count > 0 ? "nobody+#{@spec_pseudonym_count}@example.com" : "nobody@example.com")
opts[:username] ||= username
@spec_pseudonym_count += 1 if username =~ /nobody(\+\d+)?@example.com/
password = opts[:password] || "asdfasdf"
password = nil if password == :autogenerate
account = opts[:account] || Account.default
@pseudonym = account.pseudonyms.build(:user => user, :unique_id => username, :password => password, :password_confirmation => password)
@pseudonym.save_without_session_maintenance
@pseudonym.communication_channel = communication_channel(user, opts)
@pseudonym
end
def managed_pseudonym(user, opts={})
other_account = opts[:account] || account_with_saml
if other_account.password_authentication?
config = other_account.account_authorization_configs.build
config.auth_type = "saml"
config.log_in_url = opts[:saml_log_in_url] if opts[:saml_log_in_url]
config.save!
end
opts[:account] = other_account
pseudonym(user, opts)
@pseudonym.sis_user_id = opts[:sis_user_id] || "U001"
@pseudonym.save!
@pseudonym.should be_managed_password
@pseudonym
end
def user_with_managed_pseudonym(opts={})
user(opts) unless opts[:user]
user = opts[:user] || @user
managed_pseudonym(user, opts)
user
2011-02-01 09:57:29 +08:00
end
def course_with_user(enrollment_type, opts={})
@course = opts[:course] || course(opts)
@user = opts[:user] || @course.shard.activate { user(opts) }
@enrollment = @course.enroll_user(@user, enrollment_type, opts)
@enrollment.course = @course # set the reverse association
if opts[:active_enrollment] || opts[:active_all]
@enrollment.workflow_state = 'active'
@enrollment.save!
end
@course.reload
@enrollment
end
2011-02-01 09:57:29 +08:00
def course_with_student(opts={})
course_with_user('StudentEnrollment', opts)
@student = @user
@enrollment
end
def course_with_ta(opts={})
student view; closes #6995 allows course admins to view the course from a student perspective. this is accessible from a button on the course/settings page. They should be able to interact with the course as a student would, including submitting homework and quizzes. Right now there is one student view student per course, so if the course has multiple administrators, they will all share the same student view student. There are a few things that won't work in student view the way the would for a normal student, most notably access to conversations is disabled. Additionally, any publicly visible action that the teacher takes while in student view will still be publicly visible -- for example if the teacher posts a discussion topic/reply as the student view student, it will be visible to the whole class. test-plan: - (the following should be tried both as a full teacher and as a section-limited course admin) - set up a few assignments, quizzes, discussions, and module progressions in a course. - enter student view from the coures settings page. - work through the things you set up above. - leave student view from the upper right corner of the page. - as a teacher you should be able to grade the fake student so that they can continue to progress. - the student should not show up in the course users list - the student should not show up at the account level at all: * total user list * statistics Change-Id: I886a4663777f3ef2bdae594349ff6da6981e14ed Reviewed-on: https://gerrit.instructure.com/9484 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Cody Cutrer <cody@instructure.com>
2012-03-14 04:08:19 +08:00
course_with_user("TaEnrollment", opts)
@ta = @user
@enrollment
end
def course_with_student_logged_in(opts={})
course_with_student(opts)
user_session(@user)
end
def student_in_course(opts={})
opts[:course] = @course if @course && !opts[:course]
course_with_student(opts)
2011-02-01 09:57:29 +08:00
end
def student_in_section(section, opts={})
user
enrollment = section.course.enroll_user(@user, 'StudentEnrollment', :section => section)
enrollment.workflow_state = 'active'
enrollment.save!
@user
end
def teacher_in_course(opts={})
opts[:course] = @course if @course && !opts[:course]
course_with_teacher(opts)
end
2011-02-01 09:57:29 +08:00
def course_with_teacher(opts={})
course_with_user('TeacherEnrollment', opts)
@teacher = @user
2011-02-01 09:57:29 +08:00
@enrollment
end
def course_with_designer(opts={})
course_with_user('DesignerEnrollment', opts)
@designer = @user
@enrollment
end
2011-02-01 09:57:29 +08:00
def course_with_teacher_logged_in(opts={})
course_with_teacher(opts)
user_session(@user)
end
def course_with_observer(opts={})
course_with_user('ObserverEnrollment', opts)
@observer = @user
@enrollment
end
def course_with_observer_logged_in(opts={})
course_with_observer(opts)
user_session(@user)
end
remove hide_from_students from the new pages UI test plan: - start with draft state disabled - for each step, exercise the api (as a teacher) to confirm the expected values 1) create pages with different hide_from_students values a) as a teacher hidden pages should be visible; the hidden flag should persist b) as a student hidden pages should not be visible 2) using the api, update published and hide_from_students (together and separately) a) the published value should not be affected b) the hide_from_students value should be updated when set 3) enable draft state a) as a teacher previously hidden pages should be visible, but unpublished b) as a student previously hidden pages should not be visible 4) edit a page a) ensure the "Hide from students" option is gone 5) publishing pages should behave as expected a) published pages should be visible to students b) unpublished pages should not be visible to students 6) using the api, update published and hide_from_students (together and separately) a) the hide_from_students value should not be affected b) the published value should be updated when set 7) create new pages with different published states a) as a teacher unpublished pages should be visible, but unpublished b) as a student unpublished pages should not be visible 8) disable draft state a) as a teacher previously unpublished pages should be marked hidden b) as a student previously unpublished pages should not be visible fixes CNVS-7617 Change-Id: I395e0b2639543a64d9e2bc8d9377c78cf36f42d6 Reviewed-on: https://gerrit.instructure.com/23618 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: August Thornton <august@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: Bracken Mosbacker <bracken@instructure.com>
2013-08-23 04:04:59 +08:00
def set_course_draft_state(enabled=true, opts={})
course = opts[:course] || @course
account = opts[:account] || course.account
account.allow_feature!(:draft_state)
course.set_feature_flag!(:draft_state, enabled ? 'on' : 'off')
remove hide_from_students from the new pages UI test plan: - start with draft state disabled - for each step, exercise the api (as a teacher) to confirm the expected values 1) create pages with different hide_from_students values a) as a teacher hidden pages should be visible; the hidden flag should persist b) as a student hidden pages should not be visible 2) using the api, update published and hide_from_students (together and separately) a) the published value should not be affected b) the hide_from_students value should be updated when set 3) enable draft state a) as a teacher previously hidden pages should be visible, but unpublished b) as a student previously hidden pages should not be visible 4) edit a page a) ensure the "Hide from students" option is gone 5) publishing pages should behave as expected a) published pages should be visible to students b) unpublished pages should not be visible to students 6) using the api, update published and hide_from_students (together and separately) a) the hide_from_students value should not be affected b) the published value should be updated when set 7) create new pages with different published states a) as a teacher unpublished pages should be visible, but unpublished b) as a student unpublished pages should not be visible 8) disable draft state a) as a teacher previously unpublished pages should be marked hidden b) as a student previously unpublished pages should not be visible fixes CNVS-7617 Change-Id: I395e0b2639543a64d9e2bc8d9377c78cf36f42d6 Reviewed-on: https://gerrit.instructure.com/23618 Tested-by: Jenkins <jenkins@instructure.com> QA-Review: August Thornton <august@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Product-Review: Bracken Mosbacker <bracken@instructure.com>
2013-08-23 04:04:59 +08:00
enabled
end
def add_section(section_name)
@course_section = @course.course_sections.create!(:name => section_name)
@course.reload
end
def multiple_student_enrollment(user, section)
student view; closes #6995 allows course admins to view the course from a student perspective. this is accessible from a button on the course/settings page. They should be able to interact with the course as a student would, including submitting homework and quizzes. Right now there is one student view student per course, so if the course has multiple administrators, they will all share the same student view student. There are a few things that won't work in student view the way the would for a normal student, most notably access to conversations is disabled. Additionally, any publicly visible action that the teacher takes while in student view will still be publicly visible -- for example if the teacher posts a discussion topic/reply as the student view student, it will be visible to the whole class. test-plan: - (the following should be tried both as a full teacher and as a section-limited course admin) - set up a few assignments, quizzes, discussions, and module progressions in a course. - enter student view from the coures settings page. - work through the things you set up above. - leave student view from the upper right corner of the page. - as a teacher you should be able to grade the fake student so that they can continue to progress. - the student should not show up in the course users list - the student should not show up at the account level at all: * total user list * statistics Change-Id: I886a4663777f3ef2bdae594349ff6da6981e14ed Reviewed-on: https://gerrit.instructure.com/9484 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Cody Cutrer <cody@instructure.com>
2012-03-14 04:08:19 +08:00
@enrollment = @course.enroll_student(user,
:enrollment_state => "active",
:section => section,
:allow_multiple_enrollments => true)
end
def enter_student_view(opts={})
course = opts[:course] || @course || course(opts)
@fake_student = course.student_view_student
post "/users/#{@fake_student.id}/masquerade"
session[:become_user_id].should == @fake_student.id.to_s
end
option to restrict global announcements by role Gives account admins the options to display announcements to users that have a given role in at least one course. This will enable us to, e.g. write surveys intended for teachers and ensure they will not be seen by K-12 students, etc. Currently the granularity of this feature extends only to the base enrollment types, (defined in RoleOverride::ENROLLMENT_TYPES) not to the custom roles that may have been defined in a given account. Fixes CNVS-8319 Test plan - While logged in as an account admin or site admin, browse to /accounts/N/settings#tab-announcements and add a new announcement - You should see the enrollment types delineated as checkboxes - Create an announcement defined for one role but not another - Log in as a user who has one of the defined roles in at least one course to verify you are seeing the announcement - Log in as a user who has does not have any of the defined roles in any of his enrolled courses and verify that you do not see the announcement - Verify it works for "Unenrolled users" (which is not, in fact, a type of role in the same vein as the rest) by logging in as a user who is not enrolled in any courses - Check "Account admin" option with a user who does not have any role for which the announcement is defined, but is an account admin (added under the Admins section of /accounts/N/settings) Change-Id: I7d141c7c38dc05ae72991de3a710043327d615f6 Reviewed-on: https://gerrit.instructure.com/27008 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jacob Fugal <jacob@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Munda Frazier <munda@instructure.com>
2013-11-27 08:03:47 +08:00
def account_notification(opts={})
req_service = opts[:required_account_service] || nil
roles = opts[:roles] || []
message = opts[:message] || "hi there"
@account = opts[:account] || Account.default
@announcement = @account.announcements.build(message: message, required_account_service: req_service)
@announcement.start_at = opts[:start_at] || 5.minutes.ago.utc
option to restrict global announcements by role Gives account admins the options to display announcements to users that have a given role in at least one course. This will enable us to, e.g. write surveys intended for teachers and ensure they will not be seen by K-12 students, etc. Currently the granularity of this feature extends only to the base enrollment types, (defined in RoleOverride::ENROLLMENT_TYPES) not to the custom roles that may have been defined in a given account. Fixes CNVS-8319 Test plan - While logged in as an account admin or site admin, browse to /accounts/N/settings#tab-announcements and add a new announcement - You should see the enrollment types delineated as checkboxes - Create an announcement defined for one role but not another - Log in as a user who has one of the defined roles in at least one course to verify you are seeing the announcement - Log in as a user who has does not have any of the defined roles in any of his enrolled courses and verify that you do not see the announcement - Verify it works for "Unenrolled users" (which is not, in fact, a type of role in the same vein as the rest) by logging in as a user who is not enrolled in any courses - Check "Account admin" option with a user who does not have any role for which the announcement is defined, but is an account admin (added under the Admins section of /accounts/N/settings) Change-Id: I7d141c7c38dc05ae72991de3a710043327d615f6 Reviewed-on: https://gerrit.instructure.com/27008 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jacob Fugal <jacob@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Munda Frazier <munda@instructure.com>
2013-11-27 08:03:47 +08:00
@announcement.account_notification_roles.build(roles.map{ |r| { account_notification_id: @announcement.id, role_type: r} }) unless roles.empty?
@announcement.save!
end
VALID_GROUP_ATTRIBUTES = [:name, :context, :max_membership, :group_category, :join_level, :description, :is_public, :avatar_attachment]
2011-02-01 09:57:29 +08:00
def group(opts={})
context = opts[:group_context] || Account.default
@group = context.groups.create! opts.slice(*VALID_GROUP_ATTRIBUTES)
2011-02-01 09:57:29 +08:00
end
def group_with_user(opts={})
group(opts)
u = opts[:user] || user(opts)
workflow_state = opts[:active_all] ? 'accepted' : nil
@group.add_user(u, workflow_state, opts[:moderator])
end
def group_with_user_logged_in(opts={})
group_with_user(opts)
user_session(@user)
2011-02-01 09:57:29 +08:00
end
def group_category(opts = {})
context = opts[:context] || @course
@group_category = context.group_categories.create!(name: opts[:name] || 'foo')
end
def custom_role(base, name, opts={})
account = opts[:account] || @account
role = account.roles.find_by_name(name)
role ||= account.roles.create :name => name
role.base_role_type = base
role.save!
role
end
def custom_student_role(name, opts={})
custom_role('StudentEnrollment', name, opts)
end
def custom_teacher_role(name, opts={})
custom_role('TeacherEnrollment', name, opts)
end
def custom_ta_role(name, opts={})
custom_role('TaEnrollment', name, opts)
end
def custom_designer_role(name, opts={})
custom_role('DesignerEnrollment', name, opts)
end
def custom_observer_role(name, opts={})
custom_role('ObserverEnrollment', name, opts)
end
def custom_account_role(name, opts={})
custom_role(AccountUser::BASE_ROLE_NAME, name, opts)
end
2011-02-01 09:57:29 +08:00
def user_session(user, pseudonym=nil)
unless pseudonym
pseudonym = stub(:record => user, :user_id => user.id, :user => user, :login_count => 1)
# at least one thing cares about the id of the pseudonym... using the
# object_id should make it unique (but obviously things will fail if
# it tries to load it from the db.)
pseudonym.stubs(:id).returns(pseudonym.object_id)
end
session = stub(:record => pseudonym, :session_credentials => nil, :used_basic_auth? => false)
PseudonymSession.stubs(:find).returns(session)
2011-02-01 09:57:29 +08:00
end
def remove_user_session
PseudonymSession.unstub(:find)
end
def login_as(username = "nobody@example.com", password = "asdfasdf")
post_via_redirect "/login",
"pseudonym_session[unique_id]" => username,
"pseudonym_session[password]" => password
assert_response :success
path.should eql("/?login_success=1")
end
def assignment_quiz(questions, opts={})
course = opts[:course] || course(:active_course => true)
user = opts[:user] || user(:active_user => true)
course.enroll_student(user, :enrollment_state => 'active') unless user.enrollments.any? { |e| e.course_id == course.id }
@assignment = course.assignments.create(:title => "Test Assignment")
@assignment.workflow_state = "published"
@assignment.submission_types = "online_quiz"
@assignment.save
@quiz = Quiz.find_by_assignment_id(@assignment.id)
@questions = questions.map { |q| @quiz.quiz_questions.create!(q) }
@quiz.generate_quiz_data
@quiz.published_at = Time.now
@quiz.workflow_state = "available"
@quiz.save!
end
# The block should return the submission_data. A block is used so
# that we have access to the @questions variable that is created
# in this method
def quiz_with_graded_submission(questions, opts={}, &block)
assignment_quiz(questions, opts)
@quiz_submission = @quiz.generate_submission(@user)
@quiz_submission.mark_completed
@quiz_submission.submission_data = yield if block_given?
@quiz_submission.grade_submission
end
def survey_with_submission(questions, &block)
course_with_student(:active_all => true)
@assignment = @course.assignments.create(:title => "Test Assignment")
@assignment.workflow_state = "published"
@assignment.submission_types = "online_quiz"
@assignment.save
@quiz = Quiz.find_by_assignment_id(@assignment.id)
@quiz.anonymous_submissions = true
@quiz.quiz_type = "graded_survey"
@questions = questions.map { |q| @quiz.quiz_questions.create!(q) }
@quiz.generate_quiz_data
@quiz.save!
@quiz_submission = @quiz.generate_submission(@user)
@quiz_submission.mark_completed
@quiz_submission.submission_data = yield if block_given?
end
def group_discussion_assignment
course = @course || course(:active_all => true)
group_category = course.group_categories.create!(:name => "category")
@group1 = course.groups.create!(:name => "group 1", :group_category => group_category)
@group2 = course.groups.create!(:name => "group 2", :group_category => group_category)
@topic = course.discussion_topics.build(:title => "topic")
@assignment = course.assignments.build(:submission_types => 'discussion_topic', :title => @topic.title, :group_category => @group1.group_category)
make fancy midnight work for assignment overrides also fixes an issue where some dates display as "Friday at 11:59pm" instead of just "Friday" Also does a little bit of refactoring and spec backfilling for the override list presenter. The override list presenter now returns a much more friendly list of "due date" hashes to the outside world to make it easier to consume in views. Views don't have to format the dates by passing in a hash anymore. test plan: - specs should pass - as a teacher, create an assignment with overrides using the web form. In one of the overrides, enter a day like March 1 at 12am. - save the overrides - Make sure fancy midnight works for lock dates and due dates, but not unlock dates (12:00 am unlock date should show up as 12:00 am, not 11:59 pm) - on the assignment's show page, you should just see "Friday", meaning that the assignment is due at 11:59 pm on March 1. - The "fancy midnight" scheme should work correctly for assignments,quizzes,and discussion topics, including the default due dates. - Be sure to check that the dates show up correctly on the assignment,quiz, and discussion show pages. - Be sure to make an override that has a blank due_at, lock_at, and unlock_at, but has a default due date, lock date, and unlock date. The overrides should not inherit from the default due date (fixes CNVS-4216) fixes CNVS-4216, CNVS-4004, CNVS-3890 Change-Id: I8b5e10c074eb2a237a1298cb7def0cb32d3dcb7f Reviewed-on: https://gerrit.instructure.com/18142 QA-Review: Amber Taniuchi <amber@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Simon Williams <simon@instructure.com>
2013-03-06 00:04:59 +08:00
@assignment.infer_times
@assignment.saved_by = :discussion_topic
@topic.assignment = @assignment
@topic.save!
end
def rubric_for_course
@rubric = Rubric.new(:title => 'My Rubric', :context => @course)
@rubric.data = [
{
:points => 3,
:description => "First row",
:long_description => "The first row in the rubric",
:id => 1,
:ratings => [
{
:points => 3,
:description => "Rockin'",
:criterion_id => 1,
:id => 2
},
{
:points => 2,
:description => "Rockin'",
:criterion_id => 1,
:id => 3
},
{
:points => 0,
:description => "Lame",
:criterion_id => 1,
:id => 4
}
]
}
]
@rubric.save!
end
def outcome_with_rubric(opts={})
learning outcomes refactor This list is *NOT* complete, some items may have snuck in that I forgot to note, and/or some of the noted items may not be completely functional yet. Specs need to be written around a lot of this, other specs will no doubt need to be fixed. Some things, particularly around LearningOutcomeGroups will need data migrations that aren't there yet. * remove LearningOutcome.non_rubric_outcomes? and replace with false where invoked * remove LearningOutcome.enabled? and replace with true where invoked * remove never-taken branches * remove the shared/aligned_outcomes partial and it's supporting javascript, since it's now empty * remove js handler for add_outcome_alignment_link and supporting method since it only occurred in never-taken branches * mix LearningOutcomeContext into Course and Account * replace LearningOutcomeGroup.default_for(context) with LearningOutcomeContext#root_outcome_group * rename LearningOutcome#content_tags to LearningOutcome#alignments * rename LearningOutcomeGroup#content_tags to LearningOutcomeGroup#child_links, and properly restrict * remove ContentTag[Alignment]#rubric_association_id, add ContentTag[Alignment]#has_rubric_association? that looks at the presence of the content's rubric_association_id * condition off the assignment having a rubric_association rather than filtering tags by has_rubric_association (which just looks back at the assignment). all or none of the assignment's alignments are forced to have the association (via the assignment). this was true in practice before, is now codified (and more efficient) * rename AssessmentQuestionBank#learning_outcome_tags to AssessmentQuestionBank#learning_outcome_alignments * rename Assignment#learning_outcome_tags to Assignment#learning_outcome_alignments * rename Rubric#learning_outcome_tags to Rubric#learning_outcome_alignments * move/rename (Course|Account)#learning_outcome_tags to LearningOutcomeContext#learning_outcome_links * move/rename Account#learning_outcomes (corrected) and Course#learning_outcomes to LearningOutcomeContext#linked_learning_outcomes * move/rename Account#created_learning_outcomes and Course#created_learning_outcomes to LearningOutcomeContext#created_learning_outcomes * clarify and correct usage of linked_learning_outcomes vs. created_learning_outcomes * move/rename (Account|Account)#learning_outcome_groups to LearningOutcomeContext#learning_outcome_groups * remove unused Account#associated_learning_outcomes * just remove one link to a learning outcome when deleting * merge Account#has_outcomes?, Course#has_outcomes? and Course#has_outcomes into LearningOutcomeContext#has_outcomes?, add a use in Context#active_record_types * kill LearningOutcomeGroup#root_learning_outcome_group (unused) * rename LearningOutcomeResult#content_tag to LearningOutcomeResult#alignment * kill unused (and broken) OutcomesController#add_outcome_group * kill unused OutcomesController#update_outcomes_for_asset * kill unused OutcomesController#outcomes_for_asset * remove unused (outside specs, correct specs) AssessmentQuestionBank#outcomes= * remove unused ContentTag#learning_outcome_content * replace ContentTag.learning_outcome_tags_for(asset) (only ever called with asset=an assignment) with call to Assignment#learning_outcome_alignments * remove unused ContentTag.not_rubric * remove (now) unused ContentTag.include_outcome * remove unused LearningOutcome#learning_outcome_group_associations * avoid explicit use of ContentTag in outcome-related specs * replace LearningOutcomeGroup#learning_outcome_tags with LearningOutcomeGroup#child_outcome_links (and only use for outcome links; not tags for child groups) * split ContentTag#create_outcome_result into Submission#create_outcome_result, QuizSubmission#create_outcome_result, and RubricAssessment#create_outcome_result. fix some bugs along the way * refactor ContentTag.outcome_tags_for_banks and some code from QuizSubmission#(track_outcomes|update_outcomes_for_assessment_questions) into QuizSubmission#questions_and_alignments * refactor RubricAssociation#update_outcome_relations and Rubric#update_alignments into LearningOutcome.update_alignments * don't use ContentTag#rubric_association with outcome alignments; use the tag's content's rubric_association in its place (they should have been equal anyways) * refactor LearningOutcome.available_in_context and @context.root_outcome_group.sorted_all_outcomes (only time sorted_all_outcomes is used) into LearningOutcomeContext#available_outcomes and LearningOutcomeContext#available_outcome * overhaul LearningOutcomeGroup#sorted_content and rename to LearningOutcomeGroup#sorted_children. it not returns ContentTags (outcome links) and LearningOutcomeGroups, vs. LearningOutcomes and LearningOutcomeGroups; fix usages appropriately * fix UI for arranging/deleting outcome links and groups within a group to refer to the outcome link rather than the outcome Change-Id: I85d99f2634f7206332cb1f5d5ea575b428988d4b Reviewed-on: https://gerrit.instructure.com/12590 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jacob Fugal <jacob@instructure.com>
2012-07-13 01:16:13 +08:00
@outcome_group ||= @course.root_outcome_group
@outcome = @course.created_learning_outcomes.create!(:description => '<p>This is <b>awesome</b>.</p>', :short_description => 'new outcome')
learning outcomes refactor This list is *NOT* complete, some items may have snuck in that I forgot to note, and/or some of the noted items may not be completely functional yet. Specs need to be written around a lot of this, other specs will no doubt need to be fixed. Some things, particularly around LearningOutcomeGroups will need data migrations that aren't there yet. * remove LearningOutcome.non_rubric_outcomes? and replace with false where invoked * remove LearningOutcome.enabled? and replace with true where invoked * remove never-taken branches * remove the shared/aligned_outcomes partial and it's supporting javascript, since it's now empty * remove js handler for add_outcome_alignment_link and supporting method since it only occurred in never-taken branches * mix LearningOutcomeContext into Course and Account * replace LearningOutcomeGroup.default_for(context) with LearningOutcomeContext#root_outcome_group * rename LearningOutcome#content_tags to LearningOutcome#alignments * rename LearningOutcomeGroup#content_tags to LearningOutcomeGroup#child_links, and properly restrict * remove ContentTag[Alignment]#rubric_association_id, add ContentTag[Alignment]#has_rubric_association? that looks at the presence of the content's rubric_association_id * condition off the assignment having a rubric_association rather than filtering tags by has_rubric_association (which just looks back at the assignment). all or none of the assignment's alignments are forced to have the association (via the assignment). this was true in practice before, is now codified (and more efficient) * rename AssessmentQuestionBank#learning_outcome_tags to AssessmentQuestionBank#learning_outcome_alignments * rename Assignment#learning_outcome_tags to Assignment#learning_outcome_alignments * rename Rubric#learning_outcome_tags to Rubric#learning_outcome_alignments * move/rename (Course|Account)#learning_outcome_tags to LearningOutcomeContext#learning_outcome_links * move/rename Account#learning_outcomes (corrected) and Course#learning_outcomes to LearningOutcomeContext#linked_learning_outcomes * move/rename Account#created_learning_outcomes and Course#created_learning_outcomes to LearningOutcomeContext#created_learning_outcomes * clarify and correct usage of linked_learning_outcomes vs. created_learning_outcomes * move/rename (Account|Account)#learning_outcome_groups to LearningOutcomeContext#learning_outcome_groups * remove unused Account#associated_learning_outcomes * just remove one link to a learning outcome when deleting * merge Account#has_outcomes?, Course#has_outcomes? and Course#has_outcomes into LearningOutcomeContext#has_outcomes?, add a use in Context#active_record_types * kill LearningOutcomeGroup#root_learning_outcome_group (unused) * rename LearningOutcomeResult#content_tag to LearningOutcomeResult#alignment * kill unused (and broken) OutcomesController#add_outcome_group * kill unused OutcomesController#update_outcomes_for_asset * kill unused OutcomesController#outcomes_for_asset * remove unused (outside specs, correct specs) AssessmentQuestionBank#outcomes= * remove unused ContentTag#learning_outcome_content * replace ContentTag.learning_outcome_tags_for(asset) (only ever called with asset=an assignment) with call to Assignment#learning_outcome_alignments * remove unused ContentTag.not_rubric * remove (now) unused ContentTag.include_outcome * remove unused LearningOutcome#learning_outcome_group_associations * avoid explicit use of ContentTag in outcome-related specs * replace LearningOutcomeGroup#learning_outcome_tags with LearningOutcomeGroup#child_outcome_links (and only use for outcome links; not tags for child groups) * split ContentTag#create_outcome_result into Submission#create_outcome_result, QuizSubmission#create_outcome_result, and RubricAssessment#create_outcome_result. fix some bugs along the way * refactor ContentTag.outcome_tags_for_banks and some code from QuizSubmission#(track_outcomes|update_outcomes_for_assessment_questions) into QuizSubmission#questions_and_alignments * refactor RubricAssociation#update_outcome_relations and Rubric#update_alignments into LearningOutcome.update_alignments * don't use ContentTag#rubric_association with outcome alignments; use the tag's content's rubric_association in its place (they should have been equal anyways) * refactor LearningOutcome.available_in_context and @context.root_outcome_group.sorted_all_outcomes (only time sorted_all_outcomes is used) into LearningOutcomeContext#available_outcomes and LearningOutcomeContext#available_outcome * overhaul LearningOutcomeGroup#sorted_content and rename to LearningOutcomeGroup#sorted_children. it not returns ContentTags (outcome links) and LearningOutcomeGroups, vs. LearningOutcomes and LearningOutcomeGroups; fix usages appropriately * fix UI for arranging/deleting outcome links and groups within a group to refer to the outcome link rather than the outcome Change-Id: I85d99f2634f7206332cb1f5d5ea575b428988d4b Reviewed-on: https://gerrit.instructure.com/12590 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jacob Fugal <jacob@instructure.com>
2012-07-13 01:16:13 +08:00
@outcome_group.add_outcome(@outcome)
@outcome_group.save!
correctly maintain assignment/rubric <-> outcome links There are a few old LearningOutcomeResults that have a nil artifact, and many that have a Submission as an artifact. We want to get rid of these because they come from bad data, and step 1 toward that goal is to stop creating them and hide them in the UI. The LORs with a nil artifact are very old and I believe came from a very early incarnation of outcomes. The LORs with a Submission as an artifact came from a combination of two code problems. The first was old code that allowed an assignment that was aligned with an outcome but did not have a rubric to create LORs directly based on it's submission. The second was a bug that prevented the assignment <-> outcome link from being destroyed when a rubric with an outcome was removed from an assignment. fixes CNVS-7495 fixes CNVS-7498 test plan: - try different combinations of adding a rubric with an outcome to an assignment. - when you grade the assignment, the grade create a learning outcome result (which can be seen on the outcome show page, or in the account outcome report) if the rubric+outcome are currently attached to the assignment. - so for example, add a rubric with an outcome, check, remove just the outcome row, check, add a new outcome row, check, remove the whole rubric, check. - be sure to check both the show page and the outcome report TODO: - datafix migration Change-Id: I37700e3e5c08fc6cfb8fcf1cac42ea6693fcaba3 Reviewed-on: https://gerrit.instructure.com/23303 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Cameron Matheson <cameron@instructure.com> QA-Review: Amber Taniuchi <amber@instructure.com> Product-Review: Simon Williams <simon@instructure.com>
2013-08-14 07:15:15 +08:00
rubric_params = {
:title => 'My Rubric',
:hide_score_total => false,
:criteria => {
correctly maintain assignment/rubric <-> outcome links There are a few old LearningOutcomeResults that have a nil artifact, and many that have a Submission as an artifact. We want to get rid of these because they come from bad data, and step 1 toward that goal is to stop creating them and hide them in the UI. The LORs with a nil artifact are very old and I believe came from a very early incarnation of outcomes. The LORs with a Submission as an artifact came from a combination of two code problems. The first was old code that allowed an assignment that was aligned with an outcome but did not have a rubric to create LORs directly based on it's submission. The second was a bug that prevented the assignment <-> outcome link from being destroyed when a rubric with an outcome was removed from an assignment. fixes CNVS-7495 fixes CNVS-7498 test plan: - try different combinations of adding a rubric with an outcome to an assignment. - when you grade the assignment, the grade create a learning outcome result (which can be seen on the outcome show page, or in the account outcome report) if the rubric+outcome are currently attached to the assignment. - so for example, add a rubric with an outcome, check, remove just the outcome row, check, add a new outcome row, check, remove the whole rubric, check. - be sure to check both the show page and the outcome report TODO: - datafix migration Change-Id: I37700e3e5c08fc6cfb8fcf1cac42ea6693fcaba3 Reviewed-on: https://gerrit.instructure.com/23303 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Cameron Matheson <cameron@instructure.com> QA-Review: Amber Taniuchi <amber@instructure.com> Product-Review: Simon Williams <simon@instructure.com>
2013-08-14 07:15:15 +08:00
"0" => {
:points => 3,
:mastery_points => 0,
:description => "Outcome row",
:long_description => @outcome.description,
:ratings => {
"0" => {
:points => 3,
:description => "Rockin'",
},
"1" => {
:points => 0,
:description => "Lame",
}
},
:learning_outcome_id => @outcome.id
correctly maintain assignment/rubric <-> outcome links There are a few old LearningOutcomeResults that have a nil artifact, and many that have a Submission as an artifact. We want to get rid of these because they come from bad data, and step 1 toward that goal is to stop creating them and hide them in the UI. The LORs with a nil artifact are very old and I believe came from a very early incarnation of outcomes. The LORs with a Submission as an artifact came from a combination of two code problems. The first was old code that allowed an assignment that was aligned with an outcome but did not have a rubric to create LORs directly based on it's submission. The second was a bug that prevented the assignment <-> outcome link from being destroyed when a rubric with an outcome was removed from an assignment. fixes CNVS-7495 fixes CNVS-7498 test plan: - try different combinations of adding a rubric with an outcome to an assignment. - when you grade the assignment, the grade create a learning outcome result (which can be seen on the outcome show page, or in the account outcome report) if the rubric+outcome are currently attached to the assignment. - so for example, add a rubric with an outcome, check, remove just the outcome row, check, add a new outcome row, check, remove the whole rubric, check. - be sure to check both the show page and the outcome report TODO: - datafix migration Change-Id: I37700e3e5c08fc6cfb8fcf1cac42ea6693fcaba3 Reviewed-on: https://gerrit.instructure.com/23303 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Cameron Matheson <cameron@instructure.com> QA-Review: Amber Taniuchi <amber@instructure.com> Product-Review: Simon Williams <simon@instructure.com>
2013-08-14 07:15:15 +08:00
},
"1" => {
:points => 5,
:description => "no outcome row",
:long_description => 'non outcome criterion',
:ratings => {
"0" => {
:points => 5,
:description => "Amazing",
},
"1" => {
:points => 3,
:description => "not too bad",
},
"2" => {
:points => 0,
:description => "no bueno",
}
}
correctly maintain assignment/rubric <-> outcome links There are a few old LearningOutcomeResults that have a nil artifact, and many that have a Submission as an artifact. We want to get rid of these because they come from bad data, and step 1 toward that goal is to stop creating them and hide them in the UI. The LORs with a nil artifact are very old and I believe came from a very early incarnation of outcomes. The LORs with a Submission as an artifact came from a combination of two code problems. The first was old code that allowed an assignment that was aligned with an outcome but did not have a rubric to create LORs directly based on it's submission. The second was a bug that prevented the assignment <-> outcome link from being destroyed when a rubric with an outcome was removed from an assignment. fixes CNVS-7495 fixes CNVS-7498 test plan: - try different combinations of adding a rubric with an outcome to an assignment. - when you grade the assignment, the grade create a learning outcome result (which can be seen on the outcome show page, or in the account outcome report) if the rubric+outcome are currently attached to the assignment. - so for example, add a rubric with an outcome, check, remove just the outcome row, check, add a new outcome row, check, remove the whole rubric, check. - be sure to check both the show page and the outcome report TODO: - datafix migration Change-Id: I37700e3e5c08fc6cfb8fcf1cac42ea6693fcaba3 Reviewed-on: https://gerrit.instructure.com/23303 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Cameron Matheson <cameron@instructure.com> QA-Review: Amber Taniuchi <amber@instructure.com> Product-Review: Simon Williams <simon@instructure.com>
2013-08-14 07:15:15 +08:00
}
}
}
@rubric = @course.rubrics.build
@rubric.update_criteria(rubric_params)
@rubric.reload
end
def grading_standard_for(context, opts={})
@standard = context.grading_standards.create!(
:title => opts[:title] || "My Grading Standard",
:standard_data => {
"scheme_0" => {:name => "A", :value => "0.9"},
"scheme_1" => {:name => "B", :value => "0.8"},
"scheme_2" => {:name => "C", :value => "0.7"}
})
end
2011-02-01 09:57:29 +08:00
def eportfolio(opts={})
user(opts)
@portfolio = @user.eportfolios.create!
end
2011-02-01 09:57:29 +08:00
def eportfolio_with_user(opts={})
eportfolio(opts)
end
2011-02-01 09:57:29 +08:00
def eportfolio_with_user_logged_in(opts={})
eportfolio_with_user(opts)
user_session(@user)
end
def conversation(*users)
options = users.last.is_a?(Hash) ? users.pop : {}
@conversation = (options.delete(:sender) || @me || users.shift).initiate_conversation(users, options.delete(:private))
@message = @conversation.add_message('test')
@conversation.update_attributes(options)
@conversation.reload
end
def media_object(opts={})
mo = MediaObject.new
mo.media_id = opts[:media_id] || "1234"
mo.media_type = opts[:media_type] || "video"
mo.context = opts[:context] || @course
mo.user = opts[:user] || @user
mo.save!
mo
end
ui enabling admins to search notifications adds account setting and new user permission fixes CNVS-4726 Testing Notes: ============ * To enable... * Account Settings, check Feature "Admins can view notifications" * Account permission under "Account Roles" becomes available under "Admin Tools" group. Check "View notifications" * appears under account "Admin Tools" sidebar area on "View Notifications" tab. (EX: /accounts/[account_id]/admin_tools) * Verify "View Notifications" tab does not appear if either account setting or user permission is disabled. (For AccountAdmins) * Verify a SiteAdmin is able to access the feature. * Verify that notifications are returned and displayed for a selected user and date range. * Verify it displays "No messages found" when user doesn't exist or the user exists but the date range doesn't return data. * Verify that the user_id is required when searching * Verify that the "To Date" cannot be before the "From Date". * Verify an invalid date like the word "couch" gets ignored and the actually used date/time is displayed in the overview text description. * Verify searching by dates when either or both are left blank and when both are used. * Verify that the messages automatically fetch more when you scroll down. * Verify that before the results, it displays the user's name and the dates used for the results. Change-Id: I9d2689b4760af57bbc2d15fd7d50610dcf593a7e Reviewed-on: https://gerrit.instructure.com/18629 Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Reviewed-by: Mark Ericksen <marke@instructure.com>
2013-03-15 03:36:27 +08:00
def message(opts={})
m = Message.new
m.to = opts[:to] || 'some_user'
m.from = opts[:from] || 'some_other_user'
m.subject = opts[:subject] || 'a message for you'
m.body = opts[:body] || 'nice body'
m.sent_at = opts[:sent_at] || 5.days.ago
m.workflow_state = opts[:workflow_state] || 'sent'
m.user_id = opts[:user_id] || opts[:user].try(:id)
m.path_type = opts[:path_type] || 'email'
m.root_account_id = opts[:account_id] || Account.default.id
m.save!
m
end
2011-02-01 09:57:29 +08:00
def assert_status(status=500)
response.status.to_i.should eql(status)
end
def assert_unauthorized
assert_status(401) #unauthorized
# response.headers['Status'].should eql('401 Unauthorized')
2011-02-01 09:57:29 +08:00
response.should render_template("shared/unauthorized")
end
def assert_require_login
response.should be_redirect
flash[:warning].should eql("You must be logged in to access this page")
2011-02-01 09:57:29 +08:00
end
def fixture_file_upload(path, mime_type=nil, binary=false)
Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), mime_type, binary)
end
2011-02-01 09:57:29 +08:00
def default_uploaded_data
fixture_file_upload('scribd_docs/doc.doc', 'application/msword', true)
2011-02-01 09:57:29 +08:00
end
2011-02-01 09:57:29 +08:00
def valid_gradebook_csv_content
File.read(File.expand_path(File.join(File.dirname(__FILE__), %w(fixtures default_gradebook.csv))))
end
def factory_with_protected_attributes(ar_klass, attrs, do_save = true)
obj = ar_klass.respond_to?(:new) ? ar_klass.new : ar_klass.build
attrs.each { |k, v| obj.send("#{k}=", attrs[k]) }
2011-02-01 09:57:29 +08:00
obj.save! if do_save
obj
end
def update_with_protected_attributes!(ar_instance, attrs)
attrs.each { |k, v| ar_instance.send("#{k}=", attrs[k]) }
2011-02-01 09:57:29 +08:00
ar_instance.save!
end
def update_with_protected_attributes(ar_instance, attrs)
update_with_protected_attributes!(ar_instance, attrs) rescue false
end
def process_csv_data(*lines_or_opts)
account_model unless @account
lines = lines_or_opts.reject { |thing| thing.is_a? Hash }
opts = lines_or_opts.select { |thing| thing.is_a? Hash }.inject({:allow_printing => false}, :merge)
tmp = Tempfile.new("sis_rspec")
path = "#{tmp.path}.csv"
tmp.close!
File.open(path, "w+") { |f| f.puts lines.flatten.join "\n" }
opts[:files] = [path]
importer = SIS::CSV::Import.process(@account, opts)
File.unlink path
importer
end
def process_csv_data_cleanly(*lines_or_opts)
importer = process_csv_data(*lines_or_opts)
importer.errors.should == []
importer.warnings.should == []
end
def enable_cache(new_cache = ActiveSupport::Cache::MemoryStore.new)
old_cache = RAILS_CACHE
ActionController::Base.cache_store = new_cache
old_perform_caching = ActionController::Base.perform_caching
if CANVAS_RAILS2
ActionController::Base.cache_store = new_cache
silence_warnings { Object.const_set(:RAILS_CACHE, new_cache) }
else
Switchman::DatabaseServer.all.each {|s| s.stubs(:cache_store).returns(new_cache)}
end
ActionController::Base.perform_caching = true
yield
ensure
if CANVAS_RAILS2
ActionController::Base.cache_store = old_cache
silence_warnings { Object.const_set(:RAILS_CACHE, old_cache) }
else
Switchman::DatabaseServer.all.each {|s| s.unstub(:cache_store)}
end
ActionController::Base.perform_caching = old_perform_caching
end
# enforce forgery protection, so we can verify usage of the authenticity token
def enable_forgery_protection(enable = true)
old_value = ActionController::Base.allow_forgery_protection
ActionController::Base.stubs(:allow_forgery_protection).returns(enable)
yield if block_given?
ensure
ActionController::Base.stubs(:allow_forgery_protection).returns(old_value) if block_given?
end
def start_test_http_server(requests=1)
post_lines = []
server = TCPServer.open(0)
port = server.addr[1]
post_lines = []
server_thread = Thread.new(server, post_lines) do |server, post_lines|
requests.times do
client = server.accept
content_length = 0
loop do
line = client.readline
post_lines << line.strip unless line =~ /\AHost: localhost:|\AContent-Length: /
content_length = line.split(":")[1].to_i if line.strip =~ /\AContent-Length: [0-9]+\z/
if line.strip.blank?
post_lines << client.read(content_length)
break
end
end
client.puts("HTTP/1.1 200 OK\nContent-Length: 0\n\n")
client.close
end
server.close
end
return server, server_thread, post_lines
end
def stub_kaltura
# trick kaltura into being activated
Kaltura::ClientV3.stubs(:config).returns({
'domain' => 'kaltura.example.com',
'resource_domain' => 'kaltura.example.com',
'partner_id' => '100',
'subpartner_id' => '10000',
'secret_key' => 'fenwl1n23k4123lk4hl321jh4kl321j4kl32j14kl321',
'user_secret_key' => '1234821hrj3k21hjk4j3kl21j4kl321j4kl3j21kl4j3k2l1',
'player_ui_conf' => '1',
'kcw_ui_conf' => '1',
'upload_ui_conf' => '1'
})
end
def attachment_obj_with_context(obj, opts={})
@attachment = factory_with_protected_attributes(Attachment, valid_attachment_attributes.merge(opts))
@attachment.context = obj
@attachment
end
def attachment_with_context(obj, opts={})
attachment_obj_with_context(obj, opts)
@attachment.save!
@attachment
end
def json_parse(json_string = response.body)
JSON.parse(json_string.sub(%r{^while\(1\);}, ''))
end
# inspired by http://blog.jayfields.com/2007/08/ruby-calling-methods-of-specific.html
module AttachmentStorageSwitcher
BACKENDS = %w{FileSystem S3}.map { |backend| Technoweenie::AttachmentFu::Backends.const_get(:"#{backend}Backend") }.freeze
class As #:nodoc:
private *instance_methods.select { |m| m !~ /(^__|^\W|^binding$)/ }
def initialize(subject, ancestor)
@subject = subject
@ancestor = ancestor
end
def method_missing(sym, *args, &blk)
@ancestor.instance_method(sym).bind(@subject).call(*args, &blk)
end
end
def self.included(base)
base.cattr_accessor :current_backend
base.current_backend = (base.ancestors & BACKENDS).first
# make sure we have all the backends
BACKENDS.each do |backend|
base.send(:include, backend) unless base.ancestors.include?(backend)
end
# remove the duplicate callbacks added by multiple backends
base.before_update.uniq!
BACKENDS.map(&:instance_methods).flatten.uniq.each do |method|
# overridden by Attachment anyway; don't re-overwrite it
next if Attachment.instance_method(method).owner == Attachment
if method.to_s[-1..-1] == '='
base.class_eval <<-CODE
def #{method}(arg)
self.as(self.class.current_backend).#{method} arg
end
CODE
else
base.class_eval <<-CODE
def #{method}(*args, &block)
self.as(self.class.current_backend).#{method}(*args, &block)
end
CODE
end
end
end
def as(ancestor)
@__as ||= {}
unless r = @__as[ancestor]
r = (@__as[ancestor] = As.new(self, ancestor))
end
r
end
end
def s3_storage!(opts = {:stubs => true})
Attachment.send(:include, AttachmentStorageSwitcher) unless Attachment.ancestors.include?(AttachmentStorageSwitcher)
Attachment.stubs(:current_backend).returns(Technoweenie::AttachmentFu::Backends::S3Backend)
Attachment.stubs(:s3_storage?).returns(true)
Attachment.stubs(:local_storage?).returns(false)
if opts[:stubs]
conn = mock('AWS::S3::Client')
AWS::S3::S3Object.any_instance.stubs(:client).returns(conn)
AWS::Core::Configuration.any_instance.stubs(:access_key_id).returns('stub_id')
AWS::Core::Configuration.any_instance.stubs(:secret_access_key).returns('stub_key')
AWS::S3::Bucket.any_instance.stubs(:name).returns('no-bucket')
else
if Attachment.s3_config.blank? || Attachment.s3_config[:access_key_id] == 'access_key'
pending "Please put valid S3 credentials in config/amazon_s3.yml"
end
end
Attachment.s3_storage?.should eql(true)
Attachment.local_storage?.should eql(false)
end
def local_storage!
Attachment.send(:include, AttachmentStorageSwitcher) unless Attachment.ancestors.include?(AttachmentStorageSwitcher)
Attachment.stubs(:current_backend).returns(Technoweenie::AttachmentFu::Backends::FileSystemBackend)
Attachment.stubs(:s3_storage?).returns(false)
Attachment.stubs(:local_storage?).returns(true)
Attachment.local_storage?.should eql(true)
Attachment.s3_storage?.should eql(false)
Attachment.local_storage?.should eql(true)
end
refactor jobs admin functionality to not use AR queries A set of class functions were added to Delayed::Backend::ActiveRecord for all the querying a updating functionality that the jobs admin needs, so that no direct ActiveRecord queries are needed. The /jobs UI is refactored to use these new functions. There are a few differences in behavior: The search isn't a combined wildcard search anymore. Instead, new "flavors" were added to the drop-down for strand, tag, and ID. The search box searches only the selected attribute, and it's exact match now. Specs are being updated to use these new functions as well. Eventually, no direct AR queries will be done against Jobs anywhere, so that non-AR jobs backends are possible. Also as part of this, all jobs require a queue now. Passing nil for the queue will use the default of Delayed::Worker.queue. test plan: Load /jobs, and verify that it works as before except where there are differences as described above. * Selecting flavors of jobs lists only those jobs. * Searching by ID, strand or tag works. * The hold/unhold/delete actions work in the various combinations of filtering/searching. * Linking to an individual job still works (though the query string has changed so old links don't work) * Running jobs and list of popular tags still works as expected. Change-Id: Iffd5b8c7b3d6e4b128792a9dee7b97c6dfb251dc Reviewed-on: https://gerrit.instructure.com/12632 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com> Reviewed-on: https://gerrit.instructure.com/13089 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jacob Fugal <jacob@instructure.com>
2012-08-01 04:22:52 +08:00
def run_job(job)
Delayed::Worker.new.perform(job)
end
def run_jobs
while job = Delayed::Job.get_and_lock_next_available(
'spec run_jobs',
Delayed::Worker.queue,
0,
Delayed::MAX_PRIORITY)
run_job(job)
end
end
refactor jobs admin functionality to not use AR queries A set of class functions were added to Delayed::Backend::ActiveRecord for all the querying a updating functionality that the jobs admin needs, so that no direct ActiveRecord queries are needed. The /jobs UI is refactored to use these new functions. There are a few differences in behavior: The search isn't a combined wildcard search anymore. Instead, new "flavors" were added to the drop-down for strand, tag, and ID. The search box searches only the selected attribute, and it's exact match now. Specs are being updated to use these new functions as well. Eventually, no direct AR queries will be done against Jobs anywhere, so that non-AR jobs backends are possible. Also as part of this, all jobs require a queue now. Passing nil for the queue will use the default of Delayed::Worker.queue. test plan: Load /jobs, and verify that it works as before except where there are differences as described above. * Selecting flavors of jobs lists only those jobs. * Searching by ID, strand or tag works. * The hold/unhold/delete actions work in the various combinations of filtering/searching. * Linking to an individual job still works (though the query string has changed so old links don't work) * Running jobs and list of popular tags still works as expected. Change-Id: Iffd5b8c7b3d6e4b128792a9dee7b97c6dfb251dc Reviewed-on: https://gerrit.instructure.com/12632 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com> Reviewed-on: https://gerrit.instructure.com/13089 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jacob Fugal <jacob@instructure.com>
2012-08-01 04:22:52 +08:00
def track_jobs
@jobs_tracking = Delayed::JobTracking.track { yield }
end
def created_jobs
@jobs_tracking.created
end
def expects_job_with_tag(tag, count = 1)
track_jobs do
yield
end
created_jobs.count { |j| j.tag == tag }.should == count
end
# send a multipart post request in an integration spec post_params is
# an array of [k,v] params so that the order of the params can be
# defined
def send_multipart(url, post_params = {}, http_headers = {}, method = :post)
mp = Multipart::MultipartPost.new
query, headers = mp.prepare_query(post_params)
send(method, url, query, headers.merge(http_headers))
end
def force_string_encoding(str, encoding = "UTF-8")
if str.respond_to?(:force_encoding)
str.force_encoding(encoding)
end
str
end
# from minitest, MIT licensed
def capture_io
orig_stdout, orig_stderr = $stdout, $stderr
$stdout, $stderr = StringIO.new, StringIO.new
yield
return $stdout.string, $stderr.string
ensure
$stdout, $stderr = orig_stdout, orig_stderr
end
def verify_post_matches(post_lines, expected_post_lines)
# first lines should match
post_lines[0].should == expected_post_lines[0]
# now extract the headers
post_headers = post_lines[1..post_lines.index("")]
expected_post_headers = expected_post_lines[1..expected_post_lines.index("")]
expected_post_headers << "User-Agent: Ruby"
post_headers.sort.should == expected_post_headers.sort
# now check payload
post_lines[post_lines.index(""), -1].should ==
expected_post_lines[expected_post_lines.index(""), -1]
end
def compare_json(actual, expected)
if actual.is_a?(Hash)
actual.each do |k, v|
expected_v = expected[k]
compare_json(v, expected_v)
end
elsif actual.is_a?(Array)
actual.zip(expected).each do |a, e|
compare_json(a, e)
end
else
actual.to_json.should == expected.to_json
end
end
class FakeHttpResponse
def initialize(code, body = nil, headers={})
@code = code
@body = body
@headers = headers
end
def read_body(io)
io << @body
end
def code
@code.to_s
end
def [](arg)
@headers[arg]
end
def content_type
self['content-type']
end
end
def intify_timestamps(object)
case object
when Time
object.to_i
when Hash
object.inject({}) { |memo, (k, v)| memo[intify_timestamps(k)] = intify_timestamps(v); memo }
when Array
object.map { |v| intify_timestamps(v) }
else
object
end
end
def web_conference_plugin_mock(id, settings)
mock = mock("WebConferencePlugin")
mock.stubs(:id).returns(id)
mock.stubs(:settings).returns(settings)
mock.stubs(:valid_settings?).returns(true)
mock.stubs(:enabled?).returns(true)
mock.stubs(:base).returns(nil)
mock
end
def dummy_io
fixture_file_upload('scribd_docs/doc.doc', 'application/msword', true)
end
def create_attachment_for_file_upload_submission!(submission, opts={})
submission.attachments.create! opts.merge(
:filename => "doc.doc",
:display_name => "doc.doc", :user => @user,
:uploaded_data => dummy_io)
end
def course_quiz(active=false)
@quiz = @course.quizzes.create
@quiz.workflow_state = "available" if active
@quiz.save!
@quiz
end
def n_students_in_course(n, opts={})
opts.reverse_merge active_all: true
n.times.map { student_in_course(opts); @student }
end
def consider_all_requests_local(value)
if CANVAS_RAILS2
ActionController::Base.consider_all_requests_local = value
else
Rails.application.config.consider_all_requests_local = value
end
end
def page_view_for(opts={})
@account = opts[:account] || Account.default
@context = opts[:context] || course(opts)
@request_id = opts[:request_id] || RequestContextGenerator.request_id
unless @request_id
@request_id = UUIDSingleton.instance.generate
RequestContextGenerator.stubs(:request_id => @request_id)
end
Setting.set('enable_page_views', 'db')
@page_view = PageView.new { |p|
p.send(:attributes=, {
:id => @request_id,
:url => "http://test.one/",
:session_id => "phony",
:context => @context,
:controller => opts[:controller] || 'courses',
:action => opts[:action] || 'show',
:user_request => true,
:render_time => 0.01,
:user_agent => 'None',
:account_id => @account.id,
:request_id => request_id,
:interaction_seconds => 5,
:user => @user,
:remote_ip => '192.168.0.42'
}, false)
}
@page_view.save!
@page_view
end
2011-02-01 09:57:29 +08:00
end
Dir[Rails.root+'vendor/plugins/*/spec_canvas/spec_helper.rb'].each do |f|
require f
end