allow all observers

closes MBL-10561

test plan:
- go through all the observer_alerts & observer_alert_thresholds
  endpoints and use them with a course observer

Change-Id: I64b14746969727b69b047139fdceecb89f4c123a
Reviewed-on: https://gerrit.instructure.com/151107
Reviewed-by: Matthew Sessions <msessions@instructure.com>
Tested-by: Jenkins
QA-Review: Taylor Wilson <twilson@instructure.com>
Product-Review: Taylor Wilson <twilson@instructure.com>
This commit is contained in:
Cameron Sutter 2018-05-22 15:58:44 -06:00 committed by Taylor Wilson
parent 415301357f
commit 080beef78d
22 changed files with 363 additions and 227 deletions

View File

@ -25,39 +25,34 @@ class ObserverAlertThresholdsApiController < ApplicationController
def index
thresholds = if params[:student_id]
student_id = params[:student_id]
link = @current_user.as_observer_observation_links.active.where(student: student_id).take
return render_unauthorized_action unless link
link.observer_alert_thresholds.active
@current_user.as_observer_observer_alert_thresholds.active.where(student: params[:student_id])
else
links = @current_user.as_observer_observation_links.active
return render_unauthorized_action unless links.count > 0
links.map { |uol| uol.observer_alert_thresholds.active }.flatten
@current_user.as_observer_observer_alert_thresholds.active
end
if thresholds.count > 0
thresholds = thresholds.select(&:users_are_still_linked?)
return render_unauthorized_action unless thresholds.count > 0
end
render json: thresholds.map { |threshold| observer_alert_threshold_json(threshold, @current_user, session) }
end
def show
threshold = ObserverAlertThreshold.active.find(params[:observer_alert_threshold_id])
link = @current_user.as_observer_observation_links.select { |uol| uol.id == threshold.user_observation_link.id }
return render_unauthorized_action unless link.count > 0
return render_unauthorized_action unless threshold.observer_id == @current_user.id && threshold.users_are_still_linked?
render json: observer_alert_threshold_json(threshold, @current_user, session)
end
def create
student_id = params[:student_id]
link = UserObservationLink.where(observer_id: @current_user, user_id: student_id).take
return render_unauthorized_action unless link
attrs = create_params
attrs = create_params.merge(user_observation_link: link)
threshold = link.observer_alert_thresholds.active.where(alert_type: attrs[:alert_type]).take
threshold = ObserverAlertThreshold.active.where(observer: attrs[:observer_id], student: attrs[:user_id], alert_type: attrs[:alert_type]).take
if threshold
# update if duplicate
threshold.update(threshold: attrs[:threshold])
else
threshold = link.observer_alert_thresholds.create(attrs)
threshold = ObserverAlertThreshold.create(attrs)
end
if threshold.valid?
@ -69,22 +64,20 @@ class ObserverAlertThresholdsApiController < ApplicationController
def update
threshold = ObserverAlertThreshold.active.find(params[:observer_alert_threshold_id])
link = @current_user.as_observer_observation_links.select { |uol| uol.id == threshold.user_observation_link.id }
return render_unauthorized_action unless link.count > 0
return render_unauthorized_action unless threshold.observer_id == @current_user.id && threshold.users_are_still_linked?
threshold.update(update_params)
render json: observer_alert_threshold_json(threshold.reload, @current_user, session)
end
def destroy
threshold = ObserverAlertThreshold.active.find(params[:observer_alert_threshold_id])
link = @current_user.as_observer_observation_links.select { |uol| uol.id == threshold.user_observation_link.id }
return render_unauthorized_action unless link.count > 0
return render_unauthorized_action unless threshold.observer_id == @current_user.id && threshold.users_are_still_linked?
threshold.destroy
render json: observer_alert_threshold_json(threshold, @current_user, session)
end
def create_params
params.require(:observer_alert_threshold).permit(:alert_type, :threshold)
params.require(:observer_alert_threshold).permit(:alert_type, :threshold, :observer_id, :user_id)
end
def update_params

View File

@ -24,29 +24,28 @@ class ObserverAlertsApiController < ApplicationController
before_action :require_user
def alerts_by_student
link = @current_user.as_observer_observation_links.active.where(student: params[:student_id]).take
return render_unauthorized_action unless link
all_alerts = @current_user.as_observer_observer_alerts.active.where(student: params[:student_id]).select(&:users_are_still_linked?)
alerts = Api.paginate(link.observer_alerts.active, self, api_v1_observer_alerts_by_student_url)
alerts = Api.paginate(all_alerts, self, api_v1_observer_alerts_by_student_url)
render json: alerts.map { |alert| observer_alert_json(alert, @current_user, session) }
end
def alerts_count
links = UserObservationLink.active.where(observer: @current_user)
links = links.where(user_id: params[:student_id]) if params[:student_id]
all_alerts = if params[:student_id]
ObserverAlert.unread.where(observer: @current_user, student: params[:student_id])
else
ObserverAlert.unread.where(observer: @current_user)
end
return render_unauthorized_action unless links.count > 0
alerts = ObserverAlert.unread.where(user_observation_link: links)
alerts = all_alerts.select(&:users_are_still_linked?)
render json: { unread_count: alerts.count }
end
def update
alert = ObserverAlert.find(params[:observer_alert_id])
link = alert.user_observation_link
return render_unauthorized_action unless link.observer == @current_user
return render_unauthorized_action unless alert.observer_id == @current_user.id && alert.users_are_still_linked?
case params[:workflow_state]
when 'read'

View File

@ -52,12 +52,9 @@ class AccountNotification < ActiveRecord::Base
roles = self.account_notification_roles.map(&:role_name)
return if roles.count > 0 && (roles & ['StudentEnrollment', 'ObserverEnrollment']).none?
links = UserObservationLink.active.where(:root_account => self.account)
return if links.count == 0
thresholds = ObserverAlertThreshold.active.where(user_observation_link: links, alert_type: 'institution_announcement')
thresholds = ObserverAlertThreshold.active.where(observer: User.of_account(self.account), alert_type: 'institution_announcement')
thresholds.each do |threshold|
ObserverAlert.create(user_observation_link: threshold.user_observation_link,
ObserverAlert.create(student: threshold.student, observer: threshold.observer,
observer_alert_threshold: threshold, context: self,
alert_type: 'institution_announcement', action_date: self.start_at,
title: I18n.t('Announcement posted: %{account_name}', { account_name: self.account.name}))

View File

@ -139,15 +139,13 @@ class Announcement < DiscussionTopic
return if !saved_changes.keys.include?('workflow_state') || saved_changes['workflow_state'][1] != 'active'
return if self.context_type != 'Course'
observers = self.course.enrollments.active.where(type: 'ObserverEnrollment')
observers.each do |observer|
link = UserObservationLink.active.
where(user_id: observer.associated_user_id, observer_id: observer.user_id).first
observer_enrollments = self.course.enrollments.active.where(type: 'ObserverEnrollment')
observer_enrollments.each do |enrollment|
observer = enrollment.user
threshold = ObserverAlertThreshold.where(observer: observer, alert_type: 'course_announcement').first
next unless threshold
threshold = ObserverAlertThreshold.where(user_observation_link: link, alert_type: 'course_announcement').first
next if threshold.nil?
ObserverAlert.create!(user_observation_link: link, observer_alert_threshold: threshold,
ObserverAlert.create!(observer: observer, student: threshold.student, observer_alert_threshold: threshold,
context: self, alert_type: 'course_announcement', action_date: self.updated_at,
title: I18n.t("Announcement posted: %{title}", title: self.title))
end

View File

@ -16,7 +16,8 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
class ObserverAlert < ActiveRecord::Base
belongs_to :user_observation_link, :inverse_of => :observer_alerts
belongs_to :student, :class_name => 'User', inverse_of: :as_student_observer_alerts, :foreign_key => :user_id
belongs_to :observer, :class_name => 'User', inverse_of: :as_observer_observer_alerts
belongs_to :observer_alert_threshold, :inverse_of => :observer_alerts
belongs_to :context, polymorphic: [:discussion_topic, :assignment, :course, :account_notification]
@ -30,11 +31,25 @@ class ObserverAlert < ActiveRecord::Base
institution_announcement
).freeze
validates :alert_type, inclusion: { in: ALERT_TYPES }
validates :user_observation_link_id, :observer_alert_threshold_id, :alert_type, :action_date, :title, presence: true
validates :user_id, :observer_id, :observer_alert_threshold_id, :alert_type, :action_date, :title, presence: true
validate :validate_users_link
scope :active, -> { where.not(workflow_state: ['dismissed', 'deleted']) }
scope :unread, -> { where(workflow_state: 'unread') }
def validate_users_link
unless users_are_still_linked?
errors.add(:observer_id, "Observer must be linked to Student")
end
end
# TODO: search cross-shard enrollments
def users_are_still_linked?
return true if observer.as_observer_observation_links.active.where(student: student).any?
return true if observer.enrollments.active.where(associated_user: student).any?
false
end
def self.clean_up_old_alerts
ObserverAlert.where('created_at < ?', 6.months.ago).delete_all
end

View File

@ -16,7 +16,8 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
class ObserverAlertThreshold < ActiveRecord::Base
belongs_to :user_observation_link, :inverse_of => :observer_alert_thresholds
belongs_to :student, :class_name => 'User', inverse_of: :as_student_observer_alert_thresholds, :foreign_key => :user_id
belongs_to :observer, :class_name => 'User', inverse_of: :as_observer_observer_alert_thresholds
has_many :observer_alerts, :inverse_of => :observer_alert_threshold
ALERT_TYPES = %w(
@ -29,11 +30,25 @@ class ObserverAlertThreshold < ActiveRecord::Base
institution_announcement
).freeze
validates :alert_type, inclusion: { in: ALERT_TYPES }
validates :user_observation_link_id, :alert_type, presence: true
validates :alert_type, uniqueness: { scope: :user_observation_link }
validates :user_id, :observer_id, :alert_type, presence: true
validates :alert_type, uniqueness: { scope: [:user_id, :observer_id] }
validate :validate_users_link
scope :active, -> { where.not(workflow_state: 'deleted') }
def validate_users_link
unless users_are_still_linked?
errors.add(:observer_id, "Observer must be linked to Student")
end
end
# TODO: search cross-shard enrollments
def users_are_still_linked?
return true if observer.as_observer_observation_links.active.where(student: student).any?
return true if observer.enrollments.active.where(associated_user: student).any?
false
end
def destroy
self.workflow_state = 'deleted'
self.save!

View File

@ -536,10 +536,7 @@ class Submission < ActiveRecord::Base
def create_alert
return unless saved_change_to_score? && self.grader_id && !self.autograded?
links = self.user.as_student_observation_links.active
return unless links.count > 0
thresholds = ObserverAlertThreshold.active.where(user_observation_link: links,
thresholds = ObserverAlertThreshold.active.where(student: self.user,
alert_type: ['assignment_grade_high', 'assignment_grade_low'])
thresholds.each do |threshold|
@ -547,7 +544,7 @@ class Submission < ActiveRecord::Base
next if threshold.alert_type == 'assignment_grade_high' && self.score < threshold_value
next if threshold.alert_type == 'assignment_grade_low' && self.score > threshold_value
ObserverAlert.create!(user_observation_link_id: threshold.user_observation_link_id,
ObserverAlert.create!(observer: threshold.observer, student: self.user,
observer_alert_threshold: threshold,
context: self.assignment, alert_type: threshold.alert_type, action_date: self.graded_at,
title: I18n.t("Assignment graded: %{score} on %{assignmentName} in %{courseName}", {

View File

@ -68,6 +68,16 @@ class User < ActiveRecord::Base
has_many :as_observer_observation_links, -> { where.not(:workflow_state => 'deleted') }, class_name: 'UserObservationLink',
foreign_key: :observer_id, dependent: :destroy, inverse_of: :observer
has_many :as_student_observer_alert_thresholds, -> { where.not(workflow_state: 'deleted') }, class_name: 'ObserverAlertThreshold',
foreign_key: :user_id, dependent: :destroy, inverse_of: :student
has_many :as_student_observer_alerts, -> { where.not(workflow_state: 'deleted') }, class_name: 'ObserverAlert',
foreign_key: :user_id, dependent: :destroy, inverse_of: :student
has_many :as_observer_observer_alert_thresholds, -> { where.not(workflow_state: 'deleted') }, class_name: 'ObserverAlertThreshold',
foreign_key: :observer_id, dependent: :destroy, inverse_of: :observer
has_many :as_observer_observer_alerts, -> { where.not(workflow_state: 'deleted') }, class_name: 'ObserverAlert',
foreign_key: :observer_id, dependent: :destroy, inverse_of: :observer
has_many :linked_observers, -> { distinct }, :through => :as_student_observation_links, :source => :observer, :class_name => 'User'
has_many :linked_students, -> { distinct }, :through => :as_observer_observation_links, :source => :student, :class_name => 'User'

View File

@ -27,9 +27,6 @@ class UserObservationLink < ActiveRecord::Base
belongs_to :observer, :class_name => 'User', inverse_of: :as_observer_observation_links
belongs_to :root_account, :class_name => 'Account'
has_many :observer_alert_thresholds, :inverse_of => :user_observation_link
has_many :observer_alerts, :inverse_of => :user_observation_link
after_create :create_linked_enrollments
validate :not_same_user, :if => lambda { |uo| uo.changed? }

View File

@ -1322,11 +1322,6 @@ CanvasRails::Application.routes.draw do
post 'users/self/pandata_token', controller: 'users', action: 'pandata_token'
scope(controller: :observer_alerts_api) do
get '/users/:user_id/observer_alerts/unread_count', action: :alerts_count
get 'users/:user_id/observer_alerts/:student_id', action: :alerts_by_student
end
scope(controller: :user_observees) do
get 'users/:user_id/observees', action: :index, as: 'user_observees'
post 'users/:user_id/observees', action: :create
@ -1335,6 +1330,12 @@ CanvasRails::Application.routes.draw do
delete 'users/:user_id/observees/:observee_id', action: :destroy
end
scope(controller: :observer_alerts_api) do
get 'users/:user_id/observer_alerts/unread_count', action: :alerts_count
get 'users/:user_id/observer_alerts/:student_id', action: :alerts_by_student, as: 'observer_alerts_by_student'
put 'users/:user_id/observer_alerts/:observer_alert_id/:workflow_state', action: :update
end
scope(controller: :observer_alert_thresholds_api) do
get 'users/:user_id/observer_alert_thresholds', action: :index
post 'users/:user_id/observer_alert_thresholds', action: :create
@ -1342,11 +1343,6 @@ CanvasRails::Application.routes.draw do
put 'users/:user_id/observer_alert_thresholds/:observer_alert_threshold_id', action: :update
delete 'users/:user_id/observer_alert_thresholds/:observer_alert_threshold_id', action: :destroy
end
scope(controller: :observer_alerts_api) do
get 'users/:user_id/observer_alerts/:student_id', action: :alerts_by_student, as: 'observer_alerts_by_student'
put 'users/:user_id/observer_alerts/:observer_alert_id/:workflow_state', action: :update
end
end
scope(controller: :custom_data) do

View File

@ -0,0 +1,35 @@
#
# Copyright (C) 2018 - 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_relative '20180516171715_add_index_to_observer_alert_threshold'
class FixupAddIndexToObserverAlertThreshold < ActiveRecord::Migration[5.1]
tag :predeploy
def change
ObserverAlertThreshold.delete_all # no data expected. This should be a no-op, but just in case
revert AddIndexToObserverAlertThreshold
change_table :observer_alert_thresholds do |t|
t.remove_belongs_to :user_observation_link, foreign_key: { to_table: 'user_observers' }
t.references :user, null: false, foreign_key: { to_table: 'users'}
t.references :observer, null: false, foreign_key: { to_table: 'users'}
end
add_index :observer_alert_thresholds, [:alert_type, :user_id, :observer_id], unique: true, name: 'observer_alert_thresholds_on_alert_type_and_observer_and_user'
end
end

View File

@ -0,0 +1,31 @@
#
# Copyright (C) 2018 - 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/>.
#
class FixupObserverAlert < ActiveRecord::Migration[5.1]
tag :predeploy
def change
ObserverAlert.delete_all # no data expected. This should be a no-op, but just in case
remove_column :observer_alerts, :html_url
change_table :observer_alerts do |t|
t.remove_belongs_to :user_observation_link, foreign_key: { to_table: 'user_observers' }
t.references :user, null: false, foreign_key: { to_table: 'users'}
t.references :observer, null: false, foreign_key: { to_table: 'users'}
end
end
end

View File

@ -24,7 +24,8 @@ module Api::V1::ObserverAlert
:only => %w(
id
title
user_observation_link_id
user_id
observer_id
observer_alert_threshold_id
alert_type
context_type

View File

@ -23,7 +23,8 @@ module Api::V1::ObserverAlertThreshold
API_ALLOWED_OUTPUT_FIELDS = {
:only => %w(
id
user_observation_link_id
user_id
observer_id
alert_type
threshold
workflow_state

View File

@ -22,48 +22,63 @@ describe ObserverAlertThresholdsApiController, type: :request do
include Api
include Api::V1::ObserverAlertThreshold
context '#index' do
describe '#index' do
before :once do
observer_alert_threshold_model(alert_type: 'assignment_missing')
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds?student_id=#{@observee.id}"
@params = {user_id: @observer.to_param, student_id: @observee.to_param,
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds?student_id=#{@student.id}"
@params = {user_id: @observer.to_param, student_id: @student.to_param,
controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
end
describe 'with student_id' do
context 'with student_id' do
it 'returns the thresholds' do
json = api_call_as_user(@observer, :get, @path, @params)
expect(json.length).to eq 1
expect(json[0]['user_observation_link_id']).to eq @observation_link.id
expect(json[0]['user_id']).to eq @student.id
expect(json[0]['observer_id']).to eq @observer.id
expect(json[0]['alert_type']).to eq 'assignment_missing'
end
it 'only returns active thresholds' do
to_destroy = @observation_link.observer_alert_thresholds.create(alert_type: 'assignment_grade_low')
to_destroy = observer_alert_threshold_model(observer: @observer, student: @student, alert_type: 'assignment_grade_low')
to_destroy.destroy!
json = api_call_as_user(@observer, :get, @path, @params)
expect(json.length).to eq 1
thresholds = @observation_link.observer_alert_thresholds.reload
expect(thresholds.count).to eq 2
count = ObserverAlertThreshold.where(observer: @observer, student: @student).count
expect(count).to eq 2
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds?student_id=#{user.id}"
params = {user_id: @observer.to_param, student_id: user.to_param,
it 'returns an empty array if there arent any thresholds' do
observer = user_model
student = user_model
UserObservationLink.create(observer: observer, student: student)
path = "/api/v1/users/#{observer.id}/observer_alert_thresholds?student_id=#{student.id}"
params = {user_id: observer.to_param, student_id: student.to_param,
controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
api_call_as_user(@observer, :get, path, params)
json = api_call_as_user(observer, :get, path, params)
expect(json.length).to eq 0
end
it 'errors if users are no longer linked' do
observer = course_with_observer(course: @course, associated_user_id: @student.id, active_all: true).user
observer_alert_threshold_model(observer: observer, student: @student, alert_type: 'course_grade_high')
observer.enrollments.active.map(&:destroy)
@observation_link.destroy
path = "/api/v1/users/#{observer.id}/observer_alert_thresholds?student_id=#{@student.id}"
params = {user_id: observer.to_param, student_id: @student.to_param,
controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
api_call_as_user(observer, :get, path, params)
expect(response.code).to eq "401"
end
end
describe 'without student_id' do
context 'without student_id' do
it 'returns the thresholds' do
link = UserObservationLink.create(observer_id: @observer, user_id: user_model)
link.observer_alert_thresholds.create(alert_type: 'assignment_grade_high')
observer_alert_threshold_model(observer: @observer, student: @student, alert_type: 'assignment_grade_high')
path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds"
params = {user_id: @observer.to_param, controller: 'observer_alert_thresholds_api',
@ -73,19 +88,32 @@ describe ObserverAlertThresholdsApiController, type: :request do
expect(json.length).to eq 2
end
it 'errors without proper user_observation_link' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds"
params = {user_id: user.to_param, controller: 'observer_alert_thresholds_api',
action: 'index', format: 'json'}
it 'returns an empty array if there arent any thresholds' do
observer = user_model
student = user_model
UserObservationLink.create(observer: observer, student: student)
path = "/api/v1/users/#{observer.id}/observer_alert_thresholds"
params = {user_id: observer.to_param, controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
api_call_as_user(user, :get, path, params)
json = api_call_as_user(observer, :get, path, params)
expect(json.length).to eq 0
end
it 'errors if users are no longer linked' do
observer = course_with_observer(course: @course, associated_user_id: @student.id, active_all: true).user
observer_alert_threshold_model(observer: observer, student: @student, alert_type: 'course_grade_high')
observer.enrollments.active.map(&:destroy)
@observation_link.destroy
path = "/api/v1/users/#{observer.id}/observer_alert_thresholds"
params = {user_id: observer.to_param, controller: 'observer_alert_thresholds_api', action: 'index', format: 'json'}
api_call_as_user(observer, :get, path, params)
expect(response.code).to eq "401"
end
end
end
context '#show' do
describe '#show' do
before :once do
observer_alert_threshold_model(alert_type: 'assignment_missing')
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
@ -96,11 +124,12 @@ describe ObserverAlertThresholdsApiController, type: :request do
it 'returns the threshold' do
json = api_call_as_user(@observer, :get, @path, @params)
expect(json['id']).to eq @observer_alert_threshold.id
expect(json['user_observation_link_id']).to eq @observation_link.id
expect(json['user_id']).to eq @student.id
expect(json['observer_id']).to eq @observer.id
expect(json['alert_type']).to eq 'assignment_missing'
end
it 'errors without proper user_observation_link' do
it 'errors if users are not linked' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
params = {user_id: user.to_param, observer_alert_threshold_id: @observer_alert_threshold.to_param,
@ -111,53 +140,55 @@ describe ObserverAlertThresholdsApiController, type: :request do
end
end
context '#create' do
describe '#create' do
before :once do
@observer = user_model
@observee = user_model
@uol = UserObservationLink.create(observer_id: @observer, user_id: @observee)
@student = user_model
@link = UserObservationLink.create(observer: @observer, student: @student)
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds"
end
it 'creates the threshold' do
create_params = {alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
create_params = {alert_type: 'assignment_grade_high', threshold: "88", observer_id: @observer.to_param, user_id: @student.to_param}
params = {user_id: @observer.to_param, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
json = api_call_as_user(@observer, :post, @path, params)
expect(json['alert_type']).to eq 'assignment_grade_high'
expect(json['user_observation_link_id']).to eq @uol.id
expect(json['user_id']).to eq @student.id
expect(json['observer_id']).to eq @observer.id
expect(json['threshold']).to eq "88"
end
it 'errors with bad student_id' do
create_params = {alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id + 100, observer_alert_threshold: create_params,
it 'errors with bad user_id' do
create_params = {alert_type: 'assignment_grade_high', threshold: "88", observer_id: @observer.to_param, user_id: @student.id + 100}
params = {user_id: @observer.to_param, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
api_call_as_user(@observer, :post, @path, params)
expect(response.code).to eq "401"
expect(response.code).to eq "400"
end
it 'errors if user_observation_link doesnt belong to user' do
it 'errors if users are not linked' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds"
create_params = {alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: user.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
create_params = {alert_type: 'assignment_grade_high', threshold: "88", observer_id: user.to_param, user_id: @student.to_param}
params = {user_id: user.to_param, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
api_call_as_user(user, :post, path, params)
expect(response.code).to eq "401"
expect(response.code).to eq "400"
end
it 'errors without required params' do
create_params = {threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
create_params = {threshold: "88", observer_id: @observer.to_param, user_id: @student.to_param}
params = {user_id: @observer.to_param, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
api_call_as_user(@observer, :post, @path, params)
expect(response.code).to eq "400"
end
it 'ignores improper params' do
create_params = {something_sneaky: 'sneaky!', alert_type: 'assignment_grade_high', threshold: "88"}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
create_params = {something_sneaky: 'sneaky!', alert_type: 'assignment_grade_high',
threshold: "88", observer_id: @observer.to_param, user_id: @student.to_param}
params = {user_id: @observer.to_param, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
json = api_call_as_user(@observer, :post, @path, params)
expect(response.code).to eq "200"
@ -165,18 +196,18 @@ describe ObserverAlertThresholdsApiController, type: :request do
end
it 'updates if threshold already exists' do
observer_alert_threshold_model(uol: @uol, alert_type: 'assignment_grade_low', threshold: '50')
create_params = {alert_type: 'assignment_grade_low', threshold: '65'}
params = {user_id: @observer.to_param, student_id: @uol.user_id, observer_alert_threshold: create_params,
observer_alert_threshold_model(observer: @observer, student: @student, alert_type: 'assignment_grade_low', threshold: '50')
create_params = {alert_type: 'assignment_grade_low', threshold: '65', observer_id: @observer.to_param, user_id: @student.to_param}
params = {user_id: @observer.to_param, observer_alert_threshold: create_params,
controller: 'observer_alert_thresholds_api', action: 'create', format: 'json'}
json = api_call_as_user(@observer, :post, @path, params)
expect(json['id']).to eq @observer_alert_threshold.id
expect(json['threshold']).to eq '65'
expect(@uol.observer_alert_thresholds.active.where(alert_type: 'assignment_grade_low').count).to eq 1
expect(ObserverAlertThreshold.active.where(observer: @observer, student: @student, alert_type: 'assignment_grade_low').count).to eq 1
end
end
context '#update' do
describe '#update' do
before :once do
observer_alert_threshold_model(alert_type: 'assignment_grade_low', threshold: "88")
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
@ -185,16 +216,16 @@ describe ObserverAlertThresholdsApiController, type: :request do
end
it 'updates the threshold' do
update_params = {threshold: "50", alert_type: "assignment_missing",
user_observation_link_id: @observation_link.id + 100}
update_params = {threshold: "50", alert_type: "assignment_missing"}
params = @params.merge({observer_alert_threshold: update_params})
json = api_call_as_user(@observer, :put, @path, params)
expect(json['alert_type']).to eq 'assignment_grade_low'
expect(json['threshold']).to eq "50"
expect(json['user_observation_link_id']).to eq @observation_link.id
expect(json['user_id']).to eq @student.id
expect(json['observer_id']).to eq @observer.id
end
it 'errors without proper user_observation_link' do
it 'errors if users are not linked' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
params = @params.merge({user_id: user.to_param, observer_alert_threshold: {threshold: "50"}})
@ -204,7 +235,7 @@ describe ObserverAlertThresholdsApiController, type: :request do
end
end
context '#destroy' do
describe '#destroy' do
before :once do
observer_alert_threshold_model(alert_type: 'assignment_grade_low', threshold: "88")
@path = "/api/v1/users/#{@observer.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
@ -216,11 +247,11 @@ describe ObserverAlertThresholdsApiController, type: :request do
json = api_call_as_user(@observer, :delete, @path, @params)
expect(json['id']).to eq @observer_alert_threshold.id
thresholds = @observation_link.observer_alert_thresholds.active.reload
expect(thresholds.count).to eq 0
count = ObserverAlertThreshold.active.where(observer: @observer, student: @student).count
expect(count).to eq 0
end
it 'errors without proper user_observation_link' do
it 'errors if users are not linked' do
user = user_model
path = "/api/v1/users/#{user.id}/observer_alert_thresholds/#{@observer_alert_threshold.id}"
params = @params.merge({user_id: user.to_param})

View File

@ -22,21 +22,23 @@ describe ObserverAlertsApiController, type: :request do
include Api
include Api::V1::ObserverAlert
context '#alerts_by_student' do
describe '#alerts_by_student' do
before :once do
@course = course_model
@assignment = assignment_model(context: @course)
observer_alert_model(course: @course, alert_type: 'assignment_grade_high', context: @assignment)
@threshold = @observer_alert_threshold
observer_alert_threshold = @observer_alert_threshold
observer_alert_model(course: @course, observer: @observer, observee: @observee, uol: @observation_link,
observer_alert_model(course: @course, observer: @observer, student: @student, link: @observation_link,
alert_type: 'assignment_grade_low', context: @assignment)
observer_alert_model(course: @course, observer: @observer, observee: @observee, uol: @observation_link,
observer_alert_model(course: @course, observer: @observer, student: @student, link: @observation_link,
alert_type: 'course_grade_high', context: @course)
@path = "/api/v1/users/#{@observer.id}/observer_alerts/#{@observee.id}"
@params = {user_id: @observer.to_param, student_id: @observee.to_param,
@observer_alert_threshold = observer_alert_threshold
@path = "/api/v1/users/#{@observer.id}/observer_alerts/#{@student.id}"
@params = {user_id: @observer.to_param, student_id: @student.to_param,
controller: 'observer_alerts_api', action: 'alerts_by_student', format: 'json'}
end
@ -52,8 +54,9 @@ describe ObserverAlertsApiController, type: :request do
expect(alert['alert_type']).to eq('assignment_grade_high')
expect(alert['workflow_state']).to eq('unread')
expect(alert['html_url']).to eq course_assignment_url(@course, @assignment)
expect(alert['user_observation_link_id']).to eq @observation_link.id
expect(alert['observer_alert_threshold_id']).to eq @threshold.id
expect(alert['user_id']).to eq @student.id
expect(alert['observer_id']).to eq @observer.id
expect(alert['observer_alert_threshold_id']).to eq @observer_alert_threshold.id
end
it 'returns all alerts for student' do
@ -63,21 +66,21 @@ describe ObserverAlertsApiController, type: :request do
it 'doesnt return alerts for other students' do
user = user_model
uol = UserObservationLink.create(observer_id: @observer.id, user_id: user)
link = UserObservationLink.create(observer: @observer, student: user)
asg = assignment_model(context: @course)
observer_alert_model(uol: uol, alert_type: 'assignment_grade_high', context: asg)
observer_alert_model(link: link, observer: @observer, alert_type: 'assignment_grade_high', context: asg)
json = api_call_as_user(@observer, :get, @path, @params)
expect(json.length).to eq 3
end
it 'errors without valid user_observation_link' do
it 'returns empty array if users are not linked' do
user = user_model
path = "/api/v1/users/#{@observer.id}/observer_alerts/#{user.id}"
params = {user_id: @observer.to_param, student_id: user.to_param,
controller: 'observer_alerts_api', action: 'alerts_by_student', format: 'json'}
api_call_as_user(@observer, :get, path, params)
expect(response.code).to eq "401"
json = api_call_as_user(@observer, :get, path, params)
expect(json.length).to eq 0
end
end
@ -87,9 +90,10 @@ describe ObserverAlertsApiController, type: :request do
@assignment = assignment_model(context: @course)
observer_alert_model(course: @course, alert_type: 'assignment_grade_high', context: @assignment, workflow_state: 'unread')
@observee_student = @observee
student = @student
observer_alert_model(course: @course, alert_type: 'assignment_grade_high', context: @assignment, workflow_state: 'unread', observer: @observer)
observer_alert_model(course: @course, alert_type: 'assignment_grade_low', context: @assignment, workflow_state: 'read', observer: @observer)
@student = student
end
it 'only returns the number of unread alerts for the user' do
@ -100,8 +104,8 @@ describe ObserverAlertsApiController, type: :request do
end
it 'will only return the unread count for the specific student id provided' do
path = "/api/v1/users/self/observer_alerts/unread_count?student_id=#{@observee_student.id}"
params = {user_id: 'self', student_id: @observee_student.to_param, controller: 'observer_alerts_api',
path = "/api/v1/users/self/observer_alerts/unread_count?student_id=#{@student.id}"
params = {user_id: 'self', student_id: @student.to_param, controller: 'observer_alerts_api',
action: 'alerts_count', format: 'json'}
json = api_call_as_user(@observer, :get, path, params)
@ -149,7 +153,7 @@ describe ObserverAlertsApiController, type: :request do
expect(json['alert_type']).to eq 'assignment_grade_high'
end
it 'errors without valid user_observation_link' do
it 'errors if users are not linked' do
user = user_model
params = @params.merge(workflow_state: 'read')
api_call_as_user(user, :put, "#{@path}/read", params)

View File

@ -18,15 +18,16 @@
module Factories
def observer_alert_model(opts = {})
if opts[:uol]
@observation_link = opts[:uol]
else
@observee = opts[:observee] || course_with_student(opts).user
@observer = opts[:observer] || user_model
@observation_link = UserObservationLink.create!(user_id: @observee, observer_id: @observer)
end
valid_attrs = [:title, :alert_type, :workflow_state, :action_date]
opts[:observer] ||= user_model
@observer = opts[:observer]
opts[:student] ||= course_with_student(opts).user
@student = opts[:student]
@observation_link = opts[:link] || UserObservationLink.create!(student: @student, observer: @observer)
valid_attrs = [:title, :alert_type, :workflow_state, :action_date, :student, :observer]
default_attrs = {
title: 'value for type',
alert_type: 'course_announcement',
@ -36,8 +37,8 @@ module Factories
attrs = default_attrs.deep_merge(opts.slice(*valid_attrs))
if opts[:oat_id]
attrs[:observer_alert_threshold_id] = opts[:oat_id]
if opts[:threshold_id]
attrs[:observer_alert_threshold_id] = opts[:threshold_id]
else
@observer_alert_threshold = observer_alert_threshold_model(opts)
attrs[:observer_alert_threshold_id] = @observer_alert_threshold.id
@ -45,6 +46,6 @@ module Factories
attrs[:context] = opts[:context] || nil
@observer_alert = @observation_link.observer_alerts.create(attrs)
@observer_alert = ObserverAlert.create(attrs)
end
end

View File

@ -18,15 +18,15 @@
module Factories
def observer_alert_threshold_model(opts = {})
if opts[:uol]
@observation_link = opts[:uol]
else
@observee = opts[:observee] || course_with_student(opts).user
@observer = opts[:observer] || user_model
@observation_link = UserObservationLink.create!(user_id: @observee, observer_id: @observer)
end
valid_attrs = [:alert_type, :threshold, :workflow_state]
opts[:observer] ||= user_model
@observer = opts[:observer]
opts[:student] ||= course_with_student(opts).user
@student = opts[:student]
@observation_link = opts[:link] || UserObservationLink.create!(student: @student, observer: @observer)
valid_attrs = [:alert_type, :threshold, :workflow_state, :student, :observer]
default_attrs = {
alert_type: 'course_announcement',
threshold: nil,
@ -34,6 +34,6 @@ module Factories
}
attrs = default_attrs.deep_merge(opts.slice(*valid_attrs))
@observer_alert_threshold = @observation_link.observer_alert_thresholds.create(attrs)
@observer_alert_threshold = ObserverAlertThreshold.create(attrs)
end
end

View File

@ -52,7 +52,8 @@ describe "Api::V1::ObserverAlert" do
expect(json['title']).to eq('value for type')
expect(json['alert_type']).to eq('course_announcement')
expect(json['workflow_state']).to eq('unread')
expect(json['user_observation_link_id']).to eq @observation_link.id
expect(json['user_id']).to eq @student.id
expect(json['observer_id']).to eq @observer.id
expect(json['observer_alert_threshold_id']).to eq @observer_alert_threshold.id
end

View File

@ -42,7 +42,8 @@ describe "Api::V1::ObserverAlertThreshold" do
json = api.observer_alert_threshold_json(observer_alert_threshold, user, session)
expect(json['alert_type']).to eq('course_announcement')
expect(json['workflow_state']).to eq('active')
expect(json['user_observation_link_id']).to eq @observation_link.id
expect(json['user_id']).to eq @student.id
expect(json['observer_id']).to eq @observer.id
end
end
end

View File

@ -21,76 +21,84 @@ describe ObserverAlert do
include Api
include Api::V1::ObserverAlertThreshold
it 'can link to a threshold and observation link' do
assignment = assignment_model()
observer = user_factory()
student = user_factory()
link = UserObservationLink.create!(student: student, observer: observer, root_account: @account)
threshold = ObserverAlertThreshold.create!(user_observation_link: link, alert_type: 'assignment_missing')
describe 'validations' do
before :once do
@student = user_model
@observer = user_model
UserObservationLink.create(student: @student, observer: @observer)
@threshold = ObserverAlertThreshold.create!(student: @student, observer: @observer, alert_type: 'assignment_missing')
@assignment = assignment_model
end
alert = ObserverAlert.create(user_observation_link: link, observer_alert_threshold: threshold,
context: assignment, alert_type: 'assignment_missing', action_date: Time.zone.now,
title: 'Assignment missing')
it 'can link to a threshold and observer and student' do
alert = ObserverAlert.create(student: @student, observer: @observer, observer_alert_threshold: @threshold,
context: @assignment, alert_type: 'assignment_missing', action_date: Time.zone.now,
title: 'Assignment missing')
expect(alert.valid?).to eq true
expect(alert.user_observation_link).not_to be_nil
expect(alert.observer_alert_threshold).not_to be_nil
end
expect(alert.valid?).to eq true
expect(alert.user_id).not_to be_nil
expect(alert.observer_id).not_to be_nil
expect(alert.observer_alert_threshold).not_to be_nil
end
it 'wont allow random types of alert_type' do
assignment = assignment_model
link = UserObservationLink.create!(student: user_model, observer: user_model, root_account: @account)
threshold = ObserverAlertThreshold.create!(user_observation_link: link, alert_type: 'assignment_missing')
alert = ObserverAlert.create(user_observation_link: link, observer_alert_threshold: threshold,
context: assignment, alert_type: 'jigglypuff', action_date: Time.zone.now, title: 'Assignment missing')
it 'observer must be linked to student' do
alert = ObserverAlert.create(student: user_model, observer: @observer, observer_alert_threshold: @threshold,
context: @assignment, alert_type: 'assignment_missing', action_date: Time.zone.now, title: 'Assignment missing')
expect(alert.valid?).to eq false
expect(alert.valid?).to eq false
end
it 'wont allow random alert_type' do
alert = ObserverAlert.create(student: @student, observer: @observer, observer_alert_threshold: @threshold,
context: @assignment, alert_type: 'jigglypuff', action_date: Time.zone.now, title: 'Assignment missing')
expect(alert.valid?).to eq false
end
end
describe 'course_announcement' do
before :once do
@course = course_factory()
@student = student_in_course(:active_all => true, :course => @course).user
@observer = course_with_observer(:course => @course, :associated_user_id => @student.id, :active_all => true).user
@link = UserObservationLink.create!(student: @student, observer: @observer, root_account: @account)
ObserverAlertThreshold.create!(user_observation_link: @link, alert_type: 'course_announcement')
@course = course_factory
@student = student_in_course(active_all: true, course: @course).user
observer = course_with_observer(course: @course, associated_user_id: @student.id, active_all: true).user
ObserverAlertThreshold.create!(student: @student, observer: @observer, alert_type: 'course_announcement')
# user without a threshold
@observer2 = course_with_observer(:course => @course, :associated_user_id => @student.id, :active_all => true).user
@link2 = UserObservationLink.create!(student: @student, observer: @observer2, root_account: @account)
@observer2 = course_with_observer(course: @course, associated_user_id: @student.id, active_all: true).user
@observer = observer
end
it 'creates an alert when a user has a threshold for course announcements' do
a = announcement_model(:context => @course)
alert = ObserverAlert.where(user_observation_link: @link).first
alert = ObserverAlert.where(student: @student, observer: @observer).first
expect(alert).not_to be_nil
expect(alert.context).to eq a
expect(alert.title).to include('Announcement posted: ')
alert2 = ObserverAlert.where(user_observation_link: @link2).first
alert2 = ObserverAlert.where(student: @student, observer: @observer2).first
expect(alert2).to be_nil
end
it 'creates an alert when the delayed announcement becomes active' do
a = announcement_model(:context => @course, :delayed_post_at => Time.zone.now, :workflow_state => :post_delayed)
alert = ObserverAlert.where(user_observation_link: @link, context: a).first
a = announcement_model(context: @course, delayed_post_at: Time.zone.now, workflow_state: :post_delayed)
alert = ObserverAlert.where(student: @student, observer: @observer, context: a).first
expect(alert).to be_nil
a.workflow_state = 'active'
a.save!
alert = ObserverAlert.where(user_observation_link: @link, context: a).first
alert = ObserverAlert.where(student: @student, observer: @observer, context: a).first
expect(alert).not_to be_nil
end
end
describe 'clean_up_old_alerts' do
it 'deletes alerts older than 6 months ago but leaves newer ones' do
observer_alert_threshold_model()
a1 = ObserverAlert.create(user_observation_link: @observation_link, observer_alert_threshold: @observer_alert_threshold,
observer_alert_threshold_model(alert_type: 'institution_announcement')
a1 = ObserverAlert.create(student: @student, observer: @observer, observer_alert_threshold: @observer_alert_threshold,
context: @account, alert_type: 'institution_announcement', title: 'announcement',
action_date: Time.zone.now, created_at: 6.months.ago)
a2 = ObserverAlert.create(user_observation_link: @observation_link, observer_alert_threshold: @observer_alert_threshold,
a2 = ObserverAlert.create(student: @student, observer: @observer, observer_alert_threshold: @observer_alert_threshold,
context: @account, alert_type: 'institution_announcement', title: 'announcement',
action_date: Time.zone.now)
@ -103,15 +111,10 @@ describe ObserverAlert do
describe 'institution_announcement' do
before :once do
@no_link_account = account_model
@account = account_model
@student = student_in_course(:active_all => true, :course => @course).user
@observer = course_with_observer(:course => @course, :associated_user_id => @student.id, :active_all => true).user
@link = UserObservationLink.create!(student: @student, observer: @observer, root_account: @account)
ObserverAlertThreshold.create!(user_observation_link: @link, alert_type: 'institution_announcement')
# user without a threshold
@observer2 = course_with_observer(:course => @course, :associated_user_id => @student.id, :active_all => true).user
@link2 = UserObservationLink.create!(student: @student, observer: @observer2, root_account: @account)
@student = student_in_course(active_all: true).user
@account = @course.account
@observer = course_with_observer(course: @course, associated_user_id: @student.id, active_all: true).user
@threshold = ObserverAlertThreshold.create!(student: @student, observer: @observer, alert_type: 'institution_announcement')
end
it 'doesnt create an alert if the notificaiton is not for the root account' do
@ -165,7 +168,7 @@ describe ObserverAlert do
describe 'assignment_grade' do
before :once do
course_with_teacher()
course_with_teacher
@threshold1 = observer_alert_threshold_model(alert_type: 'assignment_grade_high', threshold: '80', course: @course)
@threshold2 = observer_alert_threshold_model(alert_type: 'assignment_grade_low', threshold: '40', course: @course)
assignment_model(context: @course)
@ -181,8 +184,7 @@ describe ObserverAlert do
it 'doesnt create an alert if there is no threshold for that observer' do
student = student_in_course(course: @course).user
observer_enrollment = course_with_observer(course: @course, associated_user_id: student.id)
UserObservationLink.create!(student: student, observer: observer_enrollment.user, root_account: @account)
course_with_observer(course: @course, associated_user_id: student.id)
@assignment.grade_student(student, score: 80, grader: @teacher)
@ -191,16 +193,19 @@ describe ObserverAlert do
end
it 'doesnt create an alert if the threshold is not met' do
@assignment.grade_student(@threshold1.user_observation_link.student, score: 70, grader: @teacher)
@assignment.grade_student(@threshold2.user_observation_link.student, score: 50, grader: @teacher)
@assignment.grade_student(@threshold1.student, score: 70, grader: @teacher)
@assignment.grade_student(@threshold2.student, score: 50, grader: @teacher)
alerts = ObserverAlert.where(context: @assignment)
expect(alerts.count).to eq 0
end
it 'creates an alert if the threshold is met' do
@assignment.grade_student(@threshold1.user_observation_link.student, score: 100, grader: @teacher)
@assignment.grade_student(@threshold2.user_observation_link.student, score: 10, grader: @teacher)
@course.enroll_user(@threshold1.student, 'StudentEnrollment')
@course.enroll_user(@threshold2.student, 'StudentEnrollment')
@assignment.grade_student(@threshold1.student, score: 100, grader: @teacher)
@assignment.grade_student(@threshold2.student, score: 10, grader: @teacher)
alert1 = ObserverAlert.where(context: @assignment, alert_type: 'assignment_grade_high').first
expect(alert1).not_to be_nil

View File

@ -18,20 +18,28 @@
require_relative '../spec_helper'
describe ObserverAlertThreshold do
it 'can link to an user_observation_link' do
observer = user_factory()
student = user_factory()
link = UserObservationLink.create!(:student => student, :observer => observer,
:root_account => @account)
threshold = ObserverAlertThreshold.create(:user_observation_link => link, :alert_type => 'assignment_missing')
expect(threshold.valid?).to eq true
expect(threshold.user_observation_link).not_to be_nil
before :once do
@student = user_model
@observer = user_model
UserObservationLink.create(student: @student, observer: @observer)
end
it 'wont allow random types of alert_type' do
link = UserObservationLink.create!(student: user_model, observer: user_model, root_account: @account)
threshold = ObserverAlertThreshold.create(user_observation_link: link, alert_type: 'jigglypuff')
it 'can link to an user_observation_link' do
threshold = ObserverAlertThreshold.create(student: @student, observer: @observer, alert_type: 'assignment_missing')
expect(threshold.valid?).to eq true
expect(threshold.user_id).not_to be_nil
expect(threshold.observer_id).not_to be_nil
end
it 'wont allow random alert_type' do
threshold = ObserverAlertThreshold.create(student: @student, observer: @observer, alert_type: 'jigglypuff')
expect(threshold.valid?).to eq false
end
it 'observer must be linked to student' do
threshold = ObserverAlertThreshold.create(student: user_model, observer: @observer, alert_type: 'assignment_missing')
expect(threshold.valid?).to eq false
end