Merge pull request #7235 from opf/feature/29253/show-migrations-error

[29253] Render error message in production on pending migrations
pull/7242/head
Markus Kahl 6 years ago committed by GitHub
commit 68d3ff8a53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      app/assets/stylesheets/content/_notifications.sass
  2. 2
      app/assets/stylesheets/layout/_index.sass
  3. 30
      app/assets/stylesheets/layout/_warning_bar.sass
  4. 72
      app/controllers/application_controller.rb
  5. 9
      app/helpers/error_message_helper.rb
  6. 112
      app/helpers/errors_helper.rb
  7. 35
      app/helpers/migrations_helper.rb
  8. 17
      app/views/common/_error_base.html.erb
  9. 2
      app/views/common/_validation_error.html.erb
  10. 10
      app/views/common/error.html.erb
  11. 2
      app/views/layouts/base.html.erb
  12. 2
      app/views/projects/_form.html.erb
  13. 9
      app/views/warning_bar/_pending_migrations.html.erb
  14. 2
      app/views/warning_bar/_unsupported_browser.html.erb
  15. 4
      app/views/warning_bar/_warning_bar.html.erb
  16. 4
      config/locales/en.yml
  17. 5
      lib/open_project/configuration.rb
  18. 30
      lib/open_project/database.rb
  19. 6
      lib/open_project/logging/log_delegator.rb
  20. 4
      lib/open_project/static/links.rb
  21. 2
      modules/avatars/spec/features/my_avatar_spec.rb
  22. 4
      modules/avatars/spec/features/user_avatar_spec.rb
  23. 2
      modules/ldap_groups/app/controllers/ldap_groups/synchronized_groups_controller.rb
  24. 2
      modules/my_project_page/spec/features/block_editing_spec.rb
  25. 2
      modules/reporting/spec/features/permissions_spec.rb
  26. 2
      modules/webhooks/spec/features/manage_webhooks_spec.rb
  27. 2
      spec/controllers/news_controller_spec.rb
  28. 2
      spec/features/admin/oauth/oauth_applications_management_spec.rb
  29. 4
      spec/features/users/password_change_spec.rb
  30. 1
      spec/views/common/validation_error.html.erb_spec.rb

@ -305,10 +305,6 @@ $nm-upload-box-padding: rem-calc(15) rem-calc(25)
ul ul
font-size: $nm-font-size font-size: $nm-font-size
margin: 0 0 0 30px margin: 0 0 0 30px
display: inline-block
h2, p
@extend .hidden-for-sighted
.close-handler .close-handler
float: none float: none
@ -356,13 +352,6 @@ $nm-upload-box-padding: rem-calc(15) rem-calc(25)
.flash + .flash .flash + .flash
margin-top: 3rem margin-top: 3rem
#errorExplanation
@extend %error-placeholder
top: rem-calc(68px)
&::before
color: $nm-color-error-icon !important
a.notification-box--target-link a.notification-box--target-link
cursor: pointer cursor: pointer
text-decoration: underline text-decoration: underline
@ -371,3 +360,15 @@ a.notification-box--target-link
#errorExplanation, #errorExplanation,
.notification-box--wrapper .notification-box--wrapper
top: 4.25rem top: 4.25rem
#errorExplanation
@extend %error-placeholder
top: rem-calc(68px)
&::before
color: $nm-color-error-icon !important
&.-inline
position: relative
top: 0
left: 0

@ -31,7 +31,7 @@
@import layout/base_mobile @import layout/base_mobile
@import layout/grid @import layout/grid
@import layout/tree_menu @import layout/tree_menu
@import layout/top_shelf @import layout/warning_bar
@import layout/top_menu @import layout/top_menu
@import layout/top_menu_mobile @import layout/top_menu_mobile
@import layout/breadcrumb @import layout/breadcrumb

@ -1,23 +1,23 @@
.top-shelf .warning-bar--wrapper
position: fixed
bottom: 0
z-index: 1001
width: 100%
.warning-bar--item
background-color: #f4f4aa background-color: #f4f4aa
padding: 10px padding: 10px
.top-shelf h1 h1
font-size: 12px font-size: 12px
font-weight: bold font-weight: bold
.top-shelf p p
font-size: 0.9rem font-size: 0.9rem
margin: 0 margin: 0
.top-shelf a a
font-weight: bold font-weight: bold
.unsupported-browser-warning--pane
position: fixed
bottom: 0
z-index: 1001
width: 100%
.unsupported-browser-warning--icon .unsupported-browser-warning--icon
cursor: pointer cursor: pointer

@ -42,6 +42,7 @@ class ApplicationController < ActionController::Base
include I18n include I18n
include Redmine::I18n include Redmine::I18n
include HookHelper include HookHelper
include ErrorsHelper
include ::OpenProject::Authentication::SessionExpiry include ::OpenProject::Authentication::SessionExpiry
include AdditionalUrlHelpers include AdditionalUrlHelpers
include OpenProjectErrorHelper include OpenProjectErrorHelper
@ -113,6 +114,10 @@ class ApplicationController < ActionController::Base
status: :bad_request status: :bad_request
end end
rescue_from StandardError do |exception|
render_500 exception: exception
end
before_action :user_setup, before_action :user_setup,
:check_if_login_required, :check_if_login_required,
:log_requesting_user, :log_requesting_user,
@ -457,73 +462,6 @@ class ApplicationController < ActionController::Base
redirect_to policy.redirect_url redirect_to policy.redirect_url
end end
def render_400(options = {})
@project = nil
render_error({ message: :notice_bad_request, status: 400 }.merge(options))
false
end
def render_403(options = {})
@project = nil
render_error({ message: :notice_not_authorized, status: 403 }.merge(options))
false
end
def render_404(options = {})
render_error({ message: :notice_file_not_found, status: 404 }.merge(options))
false
end
def render_500(options = {})
message = t(:notice_internal_server_error, app_title: Setting.app_title)
if $ERROR_INFO.is_a?(ActionView::ActionViewError)
@template.instance_variable_set('@project', nil)
@template.instance_variable_set('@status', 500)
@template.instance_variable_set('@message', message)
else
@project = nil
end
render_error({ message: message }.merge(options))
false
end
def render_optional_error_file(status_code)
user_setup unless User.current.id == session[:user_id]
case status_code
when :not_found
render_404
when :internal_server_error
render_500
else
super
end
end
# Renders an error response
def render_error(arg)
arg = { message: arg } unless arg.is_a?(Hash)
@status = arg[:status] || 500
@message = arg[:message]
if @status >= 500
op_handle_error "[Error #@status] #@message"
end
@message = l(@message) if @message.is_a?(Symbol)
respond_to do |format|
format.html do
render template: 'common/error', layout: use_layout, status: @status
end
format.any do
head @status
end
end
end
# Picks which layout to use based on the request # Picks which layout to use based on the request
# #
# @return [boolean, string] name of the layout to use or false for no layout # @return [boolean, string] name of the layout to use or false for no layout

@ -34,7 +34,7 @@ module ErrorMessageHelper
error_messages = objects.map { |o| o.errors.full_messages }.flatten error_messages = objects.map { |o| o.errors.full_messages }.flatten
render_error_messages_partial(error_messages, options[:object]) render_error_messages_partial(error_messages, options)
end end
# Will take a contract to display the errors in a rails form. # Will take a contract to display the errors in a rails form.
@ -51,7 +51,7 @@ module ErrorMessageHelper
end end
end end
render_error_messages_partial(error_messages, object) render_error_messages_partial(error_messages, { object: object })
end end
def extract_objects_from_params(params) def extract_objects_from_params(params)
@ -68,11 +68,12 @@ module ErrorMessageHelper
[objects.compact, options] [objects.compact, options]
end end
def render_error_messages_partial(messages, object) def render_error_messages_partial(messages, options)
unless messages.empty? unless messages.empty?
render partial: 'common/validation_error', render partial: 'common/validation_error',
locals: { error_messages: messages, locals: { error_messages: messages,
object_name: object.class.model_name.human } classes: options[:classes],
object_name: options[:object].class.model_name.human }
end end
end end
end end

@ -0,0 +1,112 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module ErrorsHelper
def render_400(options = {})
@project = nil
render_error({ message: :notice_bad_request, status: 400 }.merge(options))
false
end
def render_403(options = {})
@project = nil
render_error({ message: :notice_not_authorized, status: 403 }.merge(options))
false
end
def render_404(options = {})
render_error({ message: :notice_file_not_found, status: 404 }.merge(options))
false
end
def render_500(options = {})
message = t(:notice_internal_server_error, app_title: Setting.app_title)
if $ERROR_INFO.is_a?(ActionView::ActionViewError)
@template.instance_variable_set('@project', nil)
@template.instance_variable_set('@status', 500)
@template.instance_variable_set('@message', message)
else
@project = nil
end
# Append error information
if current_user.admin?
options[:message_details] = get_additional_message
end
render_error({ message: message }.merge(options))
false
end
def get_additional_message
return unless OpenProject::Configuration.migration_check_on_exceptions?
if OpenProject::Database.migrations_pending?(ensure_fresh: true)
I18n.t(:error_migrations_are_pending)
end
end
def render_optional_error_file(status_code)
user_setup unless User.current.id == session[:user_id]
case status_code
when :not_found
render_404
when :internal_server_error
render_500
else
super
end
end
# Renders an error response
def render_error(arg)
arg = { message: arg } unless arg.is_a?(Hash)
@status = arg[:status] || 500
@message = arg[:message]
if @status >= 500
op_handle_error "[Error #@status] #@message"
end
@message = l(@message) if @message.is_a?(Symbol)
@message_details = arg[:message_details]
respond_to do |format|
format.html do
render template: 'common/error', layout: use_layout, status: @status
end
format.any do
head @status
end
end
end
end

@ -0,0 +1,35 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module MigrationsHelper
def render_pending_migrations_warning?
current_user.admin? && OpenProject::Database.migrations_pending?
end
end

@ -27,13 +27,20 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<div class="errorExplanation" id="errorExplanation" tabindex="0" role="alert"> <div id="errorExplanation"
class="notification-box errorExplanation -error <%= local_assigns[:classes] || '' %>"
tabindex="0"
role="alert">
<div class="notification-box--content">
<% if content_for?(:error_details) %> <% if content_for?(:error_details) %>
<h2><%= error_message %></h2> <p>
<%= content_for :error_details %> <strong><%= error_message %></strong>
</p>
<p>
<%= content_for :error_details %>
</p>
<% else %> <% else %>
<%= error_message %> <%= error_message %>
<% end %> <% end %>
<i class="icon-close close-handler" role="button" tabindex="0" aria-label="{{ ::I18n.t('js.close_popup_title') }}"> </div>
</i>
</div> </div>

@ -41,4 +41,4 @@ See docs/COPYRIGHT.rdoc for more details.
</ul> </ul>
<% end %> <% end %>
<%= render partial: "common/error_base", locals: { error_message: error_message } %> <%= render partial: "common/error_base", locals: { error_message: error_message, classes: classes } %>

@ -27,8 +27,14 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<h2><%=h @status %></h2> <% if @message_details %>
<%= render partial: "common/error_base", locals: { error_message: (h @message) } %> <% content_for :error_details do %>
<p><%= @message_details %></p>
<% end %>
<% end %>
<% error_message = "[#{I18n.t(:error_code, code: @status)}] #{@message}" %>
<%= render partial: "common/error_base", locals: { error_message: error_message } %>
<%= call_hook(:view_common_error_details, { params: params, project: ((defined? @project) ? @project : nil) }) %> <%= call_hook(:view_common_error_details, { params: params, project: ((defined? @project) ? @project : nil) }) %>
<% html_title h(@status) %> <% html_title h(@status) %>

@ -91,7 +91,7 @@ See docs/COPYRIGHT.rdoc for more details.
<%= crowdin_in_context_translation %> <%= crowdin_in_context_translation %>
</head> </head>
<body class="<%= body_css_classes %> __overflowing_element_container __overflowing_body" data-relative_url_root="<%= root_path %>" data-overflowing-identifier=".__overflowing_body"> <body class="<%= body_css_classes %> __overflowing_element_container __overflowing_body" data-relative_url_root="<%= root_path %>" data-overflowing-identifier=".__overflowing_body">
<%= render partial: 'browser/unsupported_browser' if unsupported_browser? %> <%= render partial: 'warning_bar/warning_bar' %>
<noscript> <noscript>
<div class="top-shelf"> <div class="top-shelf">
<p> <p>

@ -27,7 +27,7 @@ See docs/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<%= error_messages_for project unless local_assigns[:no_error_messages] %> <%= error_messages_for(project, classes: '-inline') unless local_assigns[:no_error_messages] %>
<!--[form:project]--> <!--[form:project]-->

@ -0,0 +1,9 @@
<div id="pending-migrations-warning" class="warning-bar--item unsupported-browser-warning--pane">
<p>
<span class="icon3 icon-warning"></span>
<strong><%= t(:error_migrations_are_pending) %></strong>
<br/>
<%= t(:error_migrations_visit_upgrade_guides) %>:
<%= static_link_to :upgrade_guides %>
</p>
</div>

@ -1,4 +1,4 @@
<div id="unsupported-browser-warning" class="top-shelf unsupported-browser-warning--pane"> <div id="unsupported-browser-warning" class="warning-bar--item unsupported-browser-warning--pane">
<p> <p>
<span title="<%= t('unsupported_browser.close_warning') %>" class="icon3 icon-warning unsupported-browser-warning--icon"></span> <span title="<%= t('unsupported_browser.close_warning') %>" class="icon3 icon-warning unsupported-browser-warning--icon"></span>
<strong><%= t('unsupported_browser.title') %></strong> <strong><%= t('unsupported_browser.title') %></strong>

@ -0,0 +1,4 @@
<div class="warning-bar--wrapper">
<%= render partial: 'warning_bar/pending_migrations' if render_pending_migrations_warning? %>
<%= render partial: 'warning_bar/unsupported_browser' if unsupported_browser? %>
</div>

@ -1021,6 +1021,7 @@ en:
error_can_not_reopen_work_package_on_closed_version: "A work package assigned to a closed version cannot be reopened" error_can_not_reopen_work_package_on_closed_version: "A work package assigned to a closed version cannot be reopened"
error_can_not_find_all_resources: "Could not find all related resources to this request." error_can_not_find_all_resources: "Could not find all related resources to this request."
error_check_user_and_role: "Please choose a user and a role." error_check_user_and_role: "Please choose a user and a role."
error_code: "Error %{code}"
error_cookie_missing: 'The OpenProject cookie is missing. Please ensure that cookies are enabled, as this application will not properly function without.' error_cookie_missing: 'The OpenProject cookie is missing. Please ensure that cookies are enabled, as this application will not properly function without.'
error_custom_option_not_found: "Option does not exist." error_custom_option_not_found: "Option does not exist."
error_dependent_work_package: "Error in dependent work package #%{related_id} %{related_subject}: %{error}" error_dependent_work_package: "Error in dependent work package #%{related_id} %{related_subject}: %{error}"
@ -1037,6 +1038,8 @@ en:
error_work_package_done_ratios_not_updated: "Work package done ratios not updated." error_work_package_done_ratios_not_updated: "Work package done ratios not updated."
error_work_package_not_found_in_project: "The work package was not found or does not belong to this project" error_work_package_not_found_in_project: "The work package was not found or does not belong to this project"
error_must_be_project_member: "must be project member" error_must_be_project_member: "must be project member"
error_migrations_are_pending: "Your OpenProject installation has pending database migrations. You have likely missed running the migrations on your last upgrade. Please check the upgrade guide to properly upgrade your installation."
error_migrations_visit_upgrade_guides: "Please visit our upgrade guide documentation"
error_no_default_work_package_status: "No default work package status is defined. Please check your configuration (Go to \"Administration -> Work package statuses\")." error_no_default_work_package_status: "No default work package status is defined. Please check your configuration (Go to \"Administration -> Work package statuses\")."
error_no_type_in_project: "No type is associated to this project. Please check the Project settings." error_no_type_in_project: "No type is associated to this project. Please check the Project settings."
error_omniauth_registration_timed_out: "The registration via an external authentication provider timed out. Please try again." error_omniauth_registration_timed_out: "The registration via an external authentication provider timed out. Please try again."
@ -1591,6 +1594,7 @@ en:
label_updated_time: "Updated %{value} ago" label_updated_time: "Updated %{value} ago"
label_updated_time_at: "%{author} %{age}" label_updated_time_at: "%{author} %{age}"
label_updated_time_by: "Updated by %{author} %{age} ago" label_updated_time_by: "Updated by %{author} %{age} ago"
label_upgrade_guides: 'Upgrade guides'
label_used_by: "Used by" label_used_by: "Used by"
label_used_by_types: "Used by types" label_used_by_types: "Used by types"
label_used_in_projects: "Used in projects" label_used_in_projects: "Used in projects"

@ -139,7 +139,10 @@ module OpenProject
# Display update / security badge, enabled by default # Display update / security badge, enabled by default
'security_badge_displayed' => true, 'security_badge_displayed' => true,
'installation_type' => "manual", 'installation_type' => "manual",
'security_badge_url' => "https://releases.openproject.com/v1/check.svg" 'security_badge_url' => "https://releases.openproject.com/v1/check.svg",
# Check for missing migrations in internal errors
'migration_check_on_exceptions' => true,
} }
@config = nil @config = nil

@ -67,6 +67,24 @@ module OpenProject
} }
end end
##
# Check pending database migrations
# and cache the result for up to one hour
def self.migrations_pending?(ensure_fresh: false)
cache_key = OpenProject::Cache::CacheKey.key('database_migrations')
cached_result = Rails.cache.read(cache_key)
# Ensure cache is busted if result is positive or unset
# and the value was cached
if ensure_fresh || cached_result != false
fresh_result = connection.migration_context.needs_migration?
Rails.cache.write(cache_key, expires_in: 1.hour)
return fresh_result
end
false
end
## ##
# Check the database version compatibility. # Check the database version compatibility.
# Raises an +InsufficientVersionError+ when the version is incompatible # Raises an +InsufficientVersionError+ when the version is incompatible
@ -100,13 +118,19 @@ module OpenProject
# Get the raw name of the currently used database adapter. # Get the raw name of the currently used database adapter.
# This string is set by the used adapter gem. # This string is set by the used adapter gem.
def self.adapter_name(connection) def self.adapter_name(connection = self.connection)
connection.adapter_name connection.adapter_name
end end
# Get the AR base connection object handle
# will open a db connection implicitly
def self.connection
ActiveRecord::Base.connection
end
# returns the identifier of the specified connection # returns the identifier of the specified connection
# (defaults to ActiveRecord::Base.connection) # (defaults to ActiveRecord::Base.connection)
def self.name(connection = ActiveRecord::Base.connection) def self.name(connection = self.connection)
supported_adapters.find(proc { [:unknown, //] }) { |_adapter, regex| supported_adapters.find(proc { [:unknown, //] }) { |_adapter, regex|
adapter_name(connection) =~ regex adapter_name(connection) =~ regex
}[0] }[0]
@ -118,7 +142,7 @@ module OpenProject
# OpenProject::Database.mysql?(my_connection) # OpenProject::Database.mysql?(my_connection)
supported_adapters.keys.each do |adapter| supported_adapters.keys.each do |adapter|
(class << self; self; end).class_eval do (class << self; self; end).class_eval do
define_method(:"#{adapter.to_s}?") do |connection = ActiveRecord::Base.connection| define_method(:"#{adapter.to_s}?") do |connection = self.connection|
send(:name, connection) == adapter send(:name, connection) == adapter
end end
end end

@ -10,12 +10,16 @@ module OpenProject
message = message =
if exception.is_a? Exception if exception.is_a? Exception
context[:exception] = exception context[:exception] = exception
context[:backtrace] = clean_backtrace(exception)
"#{exception}: #{exception.message}" "#{exception}: #{exception.message}"
else else
exception.to_s exception.to_s
end end
# Mark backtrace
if context[:exception]
context[:backtrace] = clean_backtrace(context[:exception])
end
# Set current contexts # Set current contexts
context[:level] ||= context[:exception] ? :error : :info context[:level] ||= context[:exception] ? :error : :info
context[:current_user] ||= User.current context[:current_user] ||= User.current

@ -82,6 +82,10 @@ module OpenProject
href: 'https://www.openproject.org/help/', href: 'https://www.openproject.org/help/',
label: 'homescreen.links.user_guides' label: 'homescreen.links.user_guides'
}, },
upgrade_guides: {
href: 'https://www.openproject.org/operations/upgrading/',
label: :label_upgrade_guides
},
configuration_guide: { configuration_guide: {
href: 'https://www.openproject.org/operations/configuration/', href: 'https://www.openproject.org/operations/configuration/',
label: 'links.configuration_guide' label: 'links.configuration_guide'

@ -23,7 +23,7 @@ describe 'My avatar management', type: :feature, js: true do
it 'renders 404 when visiting and does not render the menu item' do it 'renders 404 when visiting and does not render the menu item' do
visit edit_my_avatar_path visit edit_my_avatar_path
expect(page).to have_selector('h2', text: '404') expect(page).to have_text '[Error 404]'
visit my_account_path visit my_account_path
expect(page).to have_no_selector '.avatar-menu-item' expect(page).to have_no_selector '.avatar-menu-item'

@ -21,7 +21,7 @@ describe 'User avatar management', type: :feature, js: true do
let(:target_user) { user } let(:target_user) { user }
it 'forbids the user to access' do it 'forbids the user to access' do
visit avatar_management_path visit avatar_management_path
expect(page).to have_selector('h2', text: '403') expect(page).to have_text('[Error 403]')
end end
end end
@ -31,7 +31,7 @@ describe 'User avatar management', type: :feature, js: true do
it 'forbids the user to access' do it 'forbids the user to access' do
visit avatar_management_path visit avatar_management_path
expect(page).to have_selector('h2', text: '403') expect(page).to have_text('[Error 403]')
end end
end end

@ -28,6 +28,8 @@ module LdapGroups
else else
render action: :new render action: :new
end end
rescue ActionController::ParameterMissing
render_400
end end
def destroy_info def destroy_info

@ -273,7 +273,7 @@ describe 'My project page editing', type: :feature, js: true do
let(:user) { FactoryBot.create(:user, member_in_project: project, member_through_role: role) } let(:user) { FactoryBot.create(:user, member_in_project: project, member_through_role: role) }
it 'shows a 403 error' do it 'shows a 403 error' do
expect(page).to have_selector('h2', text: '403') expect(page).to have_text '[Error 403]'
expect(page).to have_no_selector('.widget-box') expect(page).to have_no_selector('.widget-box')
end end
end end

@ -95,7 +95,7 @@ describe 'Cost report calculations', type: :feature, js: true do
let!(:permissions) { %i() } let!(:permissions) { %i() }
it 'shows nothing' do it 'shows nothing' do
expect(page).to have_selector('h2', text: '403') expect(page).to have_text '[Error 403]'
end end
end end

@ -10,7 +10,7 @@ describe 'Manage webhooks through UI', type: :feature, js: true do
it 'forbids accessing the webhooks management view' do it 'forbids accessing the webhooks management view' do
visit admin_outgoing_webhooks_path visit admin_outgoing_webhooks_path
expect(page).to have_selector('h2', text: '403') expect(page).to have_text '[Error 403]'
end end
end end

@ -145,7 +145,7 @@ describe NewsController, type: :controller do
expect(assigns(:news)).not_to be_nil expect(assigns(:news)).not_to be_nil
expect(assigns(:news)).to be_new_record expect(assigns(:news)).to be_new_record
expect(response.body).to have_selector('div#errorExplanation', text: /1 error/) expect(response.body).to have_selector('div.notification-box.-error', text: /1 error/)
end end
end end

@ -48,7 +48,7 @@ describe 'OAuth applications management', type: :feature, js: true do
click_on 'Create' click_on 'Create'
expect(page).to have_selector('#errorExplanation', text: 'Redirect URI must be an absolute URI.') expect(page).to have_selector('.errorExplanation', text: 'Redirect URI must be an absolute URI.')
fill_in 'application_redirect_uri', with: "urn:ietf:wg:oauth:2.0:oob\nhttps://localhost/my/callback" fill_in 'application_redirect_uri', with: "urn:ietf:wg:oauth:2.0:oob\nhttps://localhost/my/callback"
click_on 'Create' click_on 'Create'

@ -156,13 +156,13 @@ describe 'random password generation',
fill_in 'user_password', with: 'adminADMIN' fill_in 'user_password', with: 'adminADMIN'
fill_in 'user_password_confirmation', with: 'adminADMIN' fill_in 'user_password_confirmation', with: 'adminADMIN'
scroll_to_and_click(find('.button', text: 'Save')) scroll_to_and_click(find('.button', text: 'Save'))
expect(page).to have_selector('#errorExplanation', text: "Password Must contain characters of the following classes") expect(page).to have_selector('.errorExplanation', text: "Password Must contain characters of the following classes")
# 2 of 3 classes # 2 of 3 classes
fill_in 'user_password', with: 'adminADMIN123' fill_in 'user_password', with: 'adminADMIN123'
fill_in 'user_password_confirmation', with: 'adminADMIN123' fill_in 'user_password_confirmation', with: 'adminADMIN123'
scroll_to_and_click(find('.button', text: 'Save')) scroll_to_and_click(find('.button', text: 'Save'))
expect(page).to have_selector('#errorExplanation', text: "Password Must contain characters of the following classes") expect(page).to have_selector('.errorExplanation', text: "Password Must contain characters of the following classes")
# All classes # All classes
fill_in 'user_password', with: 'adminADMIN!' fill_in 'user_password', with: 'adminADMIN!'

@ -36,6 +36,7 @@ describe 'common/_validation_error', type: :view do
render partial: 'common/validation_error.html.erb', render partial: 'common/validation_error.html.erb',
locals: { error_messages: error_message, locals: { error_messages: error_message,
classes: 'Foo',
object_name: 'Test' } object_name: 'Test' }
end end

Loading…
Cancel
Save