summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/application_system_test_case.rb4
-rw-r--r--test/fixtures/reactions.yml51
-rw-r--r--test/functional/attachments_controller_test.rb6
-rw-r--r--test/functional/documents_controller_test.rb4
-rw-r--r--test/functional/issues_controller_test.rb38
-rw-r--r--test/functional/messages_controller_test.rb21
-rw-r--r--test/functional/news_controller_test.rb17
-rw-r--r--test/functional/reactions_controller_test.rb394
-rw-r--r--test/functional/workflows_controller_test.rb39
-rw-r--r--test/helpers/application_helper_test.rb48
-rw-r--r--test/helpers/avatars_helper_test.rb12
-rw-r--r--test/helpers/icons_helper_test.rb7
-rw-r--r--test/helpers/journals_helper_test.rb19
-rw-r--r--test/helpers/reactions_helper_test.rb216
-rw-r--r--test/integration/api_test/news_test.rb2
-rw-r--r--test/system/issues_test.rb11
-rw-r--r--test/system/oauth_provider_test.rb137
-rw-r--r--test/system/reactions_test.rb173
-rw-r--r--test/system/sudo_mode_test.rb3
-rw-r--r--test/system/timelog_test.rb3
-rw-r--r--test/unit/lib/redmine/field_format/progressbar_format_test.rb12
-rw-r--r--test/unit/lib/redmine/quote_reply_helper_test.rb2
-rw-r--r--test/unit/lib/redmine/reaction_test.rb189
-rw-r--r--test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb8
-rw-r--r--test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb12
-rw-r--r--test/unit/member_test.rb2
-rw-r--r--test/unit/reaction_test.rb118
-rw-r--r--test/unit/role_test.rb26
-rw-r--r--test/unit/setting_test.rb4
-rw-r--r--test/unit/user_test.rb94
30 files changed, 1651 insertions, 21 deletions
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
index 0bb0de944..38d69e7c8 100644
--- a/test/application_system_test_case.rb
+++ b/test/application_system_test_case.rb
@@ -73,13 +73,13 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
# using default browser locale which depend on system locale for "real" browsers drivers
def log_user(login, password)
visit '/my/page'
- assert_equal '/login', current_path
+ assert_current_path '/login', :ignore_query => true
within('#login-form form') do
fill_in 'username', :with => login
fill_in 'password', :with => password
find('input[name=login]').click
end
- assert_equal '/my/page', current_path
+ assert_current_path '/my/page', :ignore_query => true
end
def wait_for_ajax
diff --git a/test/fixtures/reactions.yml b/test/fixtures/reactions.yml
new file mode 100644
index 000000000..d8fcbfc1b
--- /dev/null
+++ b/test/fixtures/reactions.yml
@@ -0,0 +1,51 @@
+---
+reaction_001:
+ id: 1
+ reactable_type: Issue
+ reactable_id: 1
+ user_id: 1
+reaction_002:
+ id: 2
+ reactable_type: Issue
+ reactable_id: 1
+ user_id: 2
+reaction_003:
+ id: 3
+ reactable_type: Issue
+ reactable_id: 1
+ user_id: 3
+reaction_004:
+ id: 4
+ reactable_type: Journal
+ reactable_id: 1
+ user_id: 2
+reaction_005:
+ id: 5
+ reactable_type: Issue
+ reactable_id: 6
+ user_id: 2
+reaction_006:
+ id: 6
+ reactable_type: Journal
+ reactable_id: 4
+ user_id: 2
+reaction_007:
+ id: 7
+ reactable_type: News
+ reactable_id: 1
+ user_id: 1
+reaction_008:
+ id: 8
+ reactable_type: Comment
+ reactable_id: 1
+ user_id: 2
+reaction_009:
+ id: 9
+ reactable_type: Message
+ reactable_id: 7
+ user_id: 2
+reaction_010:
+ id: 10
+ reactable_type: News
+ reactable_id: 3
+ user_id: 2
diff --git a/test/functional/attachments_controller_test.rb b/test/functional/attachments_controller_test.rb
index 04fdb15d2..c2e7e2f7b 100644
--- a/test/functional/attachments_controller_test.rb
+++ b/test/functional/attachments_controller_test.rb
@@ -42,7 +42,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest
assert_response :success
assert_equal 'text/html', @response.media_type
- assert_select 'th.filename', :text => /issues_controller.rb\t\(révision 1484\)/
+ assert_select 'th.filename', :text => /issues_controller\.rb \(révision 1484\)/
assert_select 'td.line-code', :text => /Demande créée avec succès/
end
end
@@ -61,7 +61,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest
assert_response :success
assert_equal 'text/html', @response.media_type
- assert_select 'th.filename', :text => /issues_controller.rb\t\(r\?vision 1484\)/
+ assert_select 'th.filename', :text => /issues_controller\.rb \(r\?vision 1484\)/
assert_select 'td.line-code', :text => /Demande cr\?\?e avec succ\?s/
end
end
@@ -81,7 +81,7 @@ class AttachmentsControllerTest < Redmine::ControllerTest
assert_response :success
assert_equal 'text/html', @response.media_type
- assert_select 'th.filename', :text => /issues_controller.rb\t\(révision 1484\)/
+ assert_select 'th.filename', :text => /issues_controller\.rb \(révision 1484\)/
assert_select 'td.line-code', :text => /Demande créée avec succès/
end
end
diff --git a/test/functional/documents_controller_test.rb b/test/functional/documents_controller_test.rb
index b59ecdc81..944f0b30f 100644
--- a/test/functional/documents_controller_test.rb
+++ b/test/functional/documents_controller_test.rb
@@ -113,9 +113,9 @@ class DocumentsControllerTest < Redmine::ControllerTest
# adds a long description to the first document
doc = documents(:documents_001)
doc.update(:description => <<~LOREM)
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut egestas, mi vehicula varius varius, ipsum massa fermentum orci, eget tristique ante sem vel mi. Nulla facilisi. Donec enim libero, luctus ac sagittis sit amet, vehicula sagittis magna. Duis ultrices molestie ante, eget scelerisque sem iaculis vitae. Etiam fermentum mauris vitae metus pharetra condimentum fermentum est pretium. Proin sollicitudin elementum quam quis pharetra. Aenean facilisis nunc quis elit volutpat mollis. Aenean eleifend varius euismod. Ut dolor est, congue eget dapibus eget, elementum eu odio. Integer et lectus neque, nec scelerisque nisi. EndOfLineHere
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut egestas, mi vehicula varius varius, ipsum massa fermentum orci, eget tristique ante sem vel mi. Nulla facilisi. Donec enim libero, luctus ac sagittis sit amet, vehicula sagittis magna. Duis ultrices molestie ante, eget scelerisque sem iaculis vitae. Etiam fermentum mauris vitae metus pharetra condimentum fermentum est pretium. Proin sollicitudin elementum quam quis pharetra. Aenean facilisis nunc quis elit volutpat mollis. Aenean eleifend varius euismod. Ut dolor est, congue eget dapibus eget, elementum eu odio. Integer et lectus neque, nec scelerisque nisi. EndOfLineHere
- Vestibulum non velit mi. Aliquam scelerisque libero ut nulla fringilla a sollicitudin magna rhoncus. Praesent a nunc lorem, ac porttitor eros. Sed ac diam nec neque interdum adipiscing quis quis justo. Donec arcu nunc, fringilla eu dictum at, venenatis ac sem. Vestibulum quis elit urna, ac mattis sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Vestibulum non velit mi. Aliquam scelerisque libero ut nulla fringilla a sollicitudin magna rhoncus. Praesent a nunc lorem, ac porttitor eros. Sed ac diam nec neque interdum adipiscing quis quis justo. Donec arcu nunc, fringilla eu dictum at, venenatis ac sem. Vestibulum quis elit urna, ac mattis sapien. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
LOREM
get(:index, :params => {:project_id => 'ecookbook'})
assert_response :success
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
index b5180fcff..48304c868 100644
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -1737,7 +1737,7 @@ class IssuesControllerTest < Redmine::ControllerTest
assert_select 'td.last_notes[colspan="4"]', :text => 'Some notes with Redmine links: #2, r2.'
assert_select(
'td.last_notes[colspan="4"]',
- :text => 'A comment with inline image: and a reference to #1 and r2.'
+ :text => 'A comment with inline image: and a reference to #1 and r2.'
)
get(
:index,
@@ -3331,6 +3331,42 @@ class IssuesControllerTest < Redmine::ControllerTest
assert_select 'span.badge.badge-private', text: 'Private'
end
+ def test_show_should_display_reactions
+ current_user = User.generate!
+
+ User.add_to_project(current_user, projects(:projects_001),
+ Role.generate!(users_visibility: 'members_of_visible_projects', permissions: [:view_issues]))
+
+ @request.session[:user_id] = current_user.id
+
+ get :show, params: { id: 1 }
+
+ assert_response :success
+
+ assert_select 'span[data-reaction-button-id=reaction_issue_1]' do
+ # The current_user can only see members who belong to projects that the current_user has access to.
+ # Since the Redmine Admin user does not belong to any projects visible to the current_user,
+ # the Redmine Admin user's name is not displayed in the reaction user list. Instead, "1 other" is shown.
+ assert_select 'a.reaction-button[title=?]', 'Dave Lopper and John Smith' do
+ assert_select 'span.icon-label', '2'
+ end
+ end
+
+ assert_select 'span[data-reaction-button-id=reaction_journal_1]' do
+ assert_select 'a.reaction-button[title=?]', 'John Smith'
+ end
+ assert_select 'span[data-reaction-button-id=reaction_journal_2] a.reaction-button'
+ end
+
+ def test_should_not_display_reactions_when_reactions_feature_is_disabled
+ with_settings reactions_enabled: '0' do
+ get :show, params: { id: 1 }
+
+ assert_response :success
+ assert_select 'span[data-reaction-button-id]', false
+ end
+ end
+
def test_show_should_not_display_edit_attachment_icon_for_user_without_edit_issue_permission_on_tracker
role = Role.find(2)
role.set_permission_trackers 'edit_issues', [2, 3]
diff --git a/test/functional/messages_controller_test.rb b/test/functional/messages_controller_test.rb
index 74b9a3070..997b2263a 100644
--- a/test/functional/messages_controller_test.rb
+++ b/test/functional/messages_controller_test.rb
@@ -123,6 +123,27 @@ class MessagesControllerTest < Redmine::ControllerTest
assert_select 'h3', {text: /Watchers \(\d*\)/, count: 0}
end
+ def test_show_should_display_reactions
+ @request.session[:user_id] = 2
+
+ get :show, params: { board_id: 1, id: 4 }
+
+ assert_response :success
+ assert_select 'span[data-reaction-button-id=reaction_message_4] a.reaction-button' do
+ assert_select 'svg use[href*=thumb-up]'
+ end
+ assert_select 'span[data-reaction-button-id=reaction_message_5] a.reaction-button'
+ assert_select 'span[data-reaction-button-id=reaction_message_6] a.reaction-button'
+
+ # Should not display reactions when reactions feature is disabled.
+ with_settings reactions_enabled: '0' do
+ get :show, params: { board_id: 1, id: 4 }
+
+ assert_response :success
+ assert_select 'span[data-reaction-button-id]', false
+ end
+ end
+
def test_get_new
@request.session[:user_id] = 2
get(:new, :params => {:board_id => 1})
diff --git a/test/functional/news_controller_test.rb b/test/functional/news_controller_test.rb
index f1ddfff71..536814c9d 100644
--- a/test/functional/news_controller_test.rb
+++ b/test/functional/news_controller_test.rb
@@ -106,6 +106,23 @@ class NewsControllerTest < Redmine::ControllerTest
assert_response :not_found
end
+ def test_show_should_display_reactions
+ @request.session[:user_id] = 1
+
+ get :show, params: { id: 1 }
+ assert_response :success
+ assert_select 'span[data-reaction-button-id=reaction_news_1] a.reaction-button.reacted'
+ assert_select 'span[data-reaction-button-id=reaction_comment_1] a.reaction-button'
+
+ # Should not display reactions when reactions feature is disabled.
+ with_settings reactions_enabled: '0' do
+ get :show, params: { id: 1 }
+
+ assert_response :success
+ assert_select 'span[data-reaction-button-id]', false
+ end
+ end
+
def test_get_new_with_project_id
@request.session[:user_id] = 2
get(:new, :params => {:project_id => 1})
diff --git a/test/functional/reactions_controller_test.rb b/test/functional/reactions_controller_test.rb
new file mode 100644
index 000000000..b65794969
--- /dev/null
+++ b/test/functional/reactions_controller_test.rb
@@ -0,0 +1,394 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+
+require_relative '../test_helper'
+
+class ReactionsControllerTest < Redmine::ControllerTest
+ setup do
+ Setting.reactions_enabled = '1'
+ # jsmith
+ @request.session[:user_id] = users(:users_002).id
+ end
+
+ teardown do
+ Setting.clear_cache
+ end
+
+ test 'create for issue' do
+ issue = issues(:issues_002)
+
+ assert_difference(
+ ->{ Reaction.count } => 1,
+ ->{ issue.reactions.by(users(:users_002)).count } => 1
+ ) do
+ post :create, params: {
+ object_type: 'Issue',
+ object_id: issue.id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'create for journal' do
+ journal = journals(:journals_005)
+
+ assert_difference(
+ ->{ Reaction.count } => 1,
+ ->{ journal.reactions.by(users(:users_002)).count } => 1
+ ) do
+ post :create, params: {
+ object_type: 'Journal',
+ object_id: journal.id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'create for news' do
+ news = news(:news_002)
+
+ assert_difference(
+ ->{ Reaction.count } => 1,
+ ->{ news.reactions.by(users(:users_002)).count } => 1
+ ) do
+ post :create, params: {
+ object_type: 'News',
+ object_id: news.id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'create reaction for comment' do
+ comment = comments(:comments_002)
+
+ assert_difference(
+ ->{ Reaction.count } => 1,
+ ->{ comment.reactions.by(users(:users_002)).count } => 1
+ ) do
+ post :create, params: {
+ object_type: 'Comment',
+ object_id: comment.id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'create for message' do
+ message = messages(:messages_001)
+
+ assert_difference(
+ ->{ Reaction.count } => 1,
+ ->{ message.reactions.by(users(:users_002)).count } => 1
+ ) do
+ post :create, params: {
+ object_type: 'Message',
+ object_id: message.id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'destroy for issue' do
+ reaction = reactions(:reaction_005)
+
+ assert_difference 'Reaction.count', -1 do
+ delete :destroy, params: {
+ id: reaction.id,
+ # Issue (id=6)
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ assert_not Reaction.exists?(reaction.id)
+ end
+
+ test 'destroy for journal' do
+ reaction = reactions(:reaction_006)
+
+ assert_difference 'Reaction.count', -1 do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ assert_not Reaction.exists?(reaction.id)
+ end
+
+ test 'destroy for news' do
+ # For News(id=3)
+ reaction = reactions(:reaction_010)
+
+ assert_difference 'Reaction.count', -1 do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ assert_not Reaction.exists?(reaction.id)
+ end
+
+ test 'destroy for comment' do
+ # For Comment(id=1)
+ reaction = reactions(:reaction_008)
+
+ assert_difference 'Reaction.count', -1 do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ assert_not Reaction.exists?(reaction.id)
+ end
+
+ test 'destroy for message' do
+ reaction = reactions(:reaction_009)
+
+ assert_difference 'Reaction.count', -1 do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ assert_not Reaction.exists?(reaction.id)
+ end
+
+ test 'create should respond with 403 when feature is disabled' do
+ Setting.reactions_enabled = '0'
+ # admin
+ @request.session[:user_id] = users(:users_001).id
+
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Issue',
+ object_id: issues(:issues_002).id
+ }, xhr: true
+ end
+ assert_response :forbidden
+ end
+
+ test 'destroy should respond with 403 when feature is disabled' do
+ Setting.reactions_enabled = '0'
+ # admin
+ @request.session[:user_id] = users(:users_001).id
+
+ reaction = reactions(:reaction_001)
+ assert_no_difference 'Reaction.count' do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+ assert_response :forbidden
+ end
+
+ test 'create by anonymou user should respond with 401 when feature is disabled' do
+ Setting.reactions_enabled = '0'
+ @request.session[:user_id] = nil
+
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Issue',
+ object_id: issues(:issues_002).id
+ }, xhr: true
+ end
+ assert_response :unauthorized
+ end
+
+ test 'create by anonymous user should respond with 401' do
+ @request.session[:user_id] = nil
+
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Issue',
+ # Issue(id=1) is an issue in a public project
+ object_id: issues(:issues_001).id
+ }, xhr: true
+ end
+
+ assert_response :unauthorized
+ end
+
+ test 'destroy by anonymous user should respond with 401' do
+ @request.session[:user_id] = nil
+
+ reaction = reactions(:reaction_002)
+ assert_no_difference 'Reaction.count' do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :unauthorized
+ end
+
+ test 'create when reaction already exists should not create a new reaction and succeed' do
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Comment',
+ # user(jsmith) has already reacted to Comment(id=1)
+ object_id: comments(:comments_001).id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'destroy another user reaction should not destroy the reaction and succeed' do
+ # admin user's reaction
+ reaction = reactions(:reaction_001)
+
+ assert_no_difference 'Reaction.count' do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'destroy nonexistent reaction' do
+ # For Journal(id=4)
+ reaction = reactions(:reaction_006)
+ reaction.destroy!
+
+ assert_not Reaction.exists?(reaction.id)
+
+ assert_no_difference 'Reaction.count' do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :success
+ end
+
+ test 'create with invalid object type should respond with 403' do
+ # admin
+ @request.session[:user_id] = users(:users_001).id
+
+ post :create, params: {
+ object_type: 'InvalidType',
+ object_id: 1
+ }, xhr: true
+
+ assert_response :forbidden
+ end
+
+ test 'create without permission to view should respond with 403' do
+ # dlopper
+ @request.session[:user_id] = users(:users_003).id
+
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Issue',
+ # dlopper is not a member of the project where the issue (id=4) belongs.
+ object_id: issues(:issues_004).id
+ }, xhr: true
+ end
+
+ assert_response :forbidden
+ end
+
+ test 'destroy without permission to view should respond with 403' do
+ # dlopper
+ @request.session[:user_id] = users(:users_003).id
+
+ # For Issue(id=6)
+ reaction = reactions(:reaction_005)
+
+ assert_no_difference 'Reaction.count' do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :forbidden
+ end
+
+ test 'create should respond with 404 for non-JS requests' do
+ issue = issues(:issues_002)
+
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Issue',
+ object_id: issue.id
+ } # Sending an HTML request by omitting xhr: true
+ end
+
+ assert_response :not_found
+ end
+
+ test 'create should respond with 403 when project is closed' do
+ issue = issues(:issues_010)
+ issue.project.update!(status: Project::STATUS_CLOSED)
+
+ assert_no_difference 'Reaction.count' do
+ post :create, params: {
+ object_type: 'Issue',
+ object_id: issue.id
+ }, xhr: true
+ end
+
+ assert_response :forbidden
+ end
+
+ test 'destroy should respond with 403 when project is closed' do
+ reaction = reactions(:reaction_005)
+ reaction.reactable.project.update!(status: Project::STATUS_CLOSED)
+
+ assert_no_difference 'Reaction.count' do
+ delete :destroy, params: {
+ id: reaction.id,
+ object_type: reaction.reactable_type,
+ object_id: reaction.reactable_id
+ }, xhr: true
+ end
+
+ assert_response :forbidden
+ end
+end
diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb
index b30559d80..dcdc8d5bb 100644
--- a/test/functional/workflows_controller_test.rb
+++ b/test/functional/workflows_controller_test.rb
@@ -211,6 +211,45 @@ class WorkflowsControllerTest < Redmine::ControllerTest
assert w.assignee
end
+ def test_post_edit_with_large_number_of_statuses
+ # This test ensures that workflows with many statuses can be saved.
+ # Without setting `ENV['RACK_QUERY_PARSER_PARAMS_LIMIT']`, this raises
+ # ActionController::BadRequest exception due to exceeding the default
+ # query parameter limit of 4096.
+ WorkflowTransition.delete_all
+
+ num_statuses = 40
+ transitions_data = {}
+
+ # Allowed statuses for a new issue (status_id = 0)
+ transitions_data['0'] = {}
+ (1..num_statuses).each do |status_id|
+ transitions_data['0'][status_id.to_s] = {'always' => '1'}
+ end
+
+ # Status transitions between statuses
+ (1..num_statuses).each do |status_id_from| # rubocop:disable RuboCopStyle/CombinableLoops
+ transitions_data[status_id_from.to_s] = {}
+ (1..num_statuses).each do |status_id_to|
+ # skip self-transitions
+ next if status_id_from == status_id_to
+
+ transitions_data[status_id_from.to_s][status_id_to.to_s] = {
+ 'always' => '1', 'author' => '1', 'assignee' => '1'
+ }
+ end
+ end
+
+ assert_nothing_raised do
+ patch :update, :params => {
+ :role_id => 2,
+ :tracker_id => 1,
+ :transitions => transitions_data
+ }
+ end
+ assert_response :found
+ end
+
def test_get_permissions
get :permissions
diff --git a/test/helpers/application_helper_test.rb b/test/helpers/application_helper_test.rb
index f959744e2..2e2e8b933 100644
--- a/test/helpers/application_helper_test.rb
+++ b/test/helpers/application_helper_test.rb
@@ -1732,6 +1732,46 @@ class ApplicationHelperTest < Redmine::HelperTest
end
end
+ def test_section_edit_links_with_multiline_heading
+ raw = <<~RAW
+ # Wiki
+
+ ## `Foo` Bar
+
+ The heading above generates multiline HTML.
+ Don't assume heading tags are always single-line.
+
+ ```
+ <h2>
+ <code>Foo</code> Bar</h2>
+ ```
+ RAW
+ @project = Project.find(1)
+ set_language_if_valid 'en'
+ with_settings :text_formatting => 'common_mark' do
+ result =
+ textilizable(
+ raw,
+ :edit_section_links =>
+ {:controller => 'wiki', :action => 'edit',
+ :project_id => '1', :id => 'Test'}
+ ).delete("\n")
+
+ assert_match(
+ Regexp.new(
+ '<div class="contextual heading-2" title="Edit this section" id="section-2">' \
+ '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=2">' \
+ '<svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-.*\.svg#icon--edit"></use></svg>' \
+ '<span class="icon-label">Edit this section</span>' \
+ '</a></div>' \
+ '<a name="Foo-Bar"></a>' \
+ '<h2 ><code>Foo</code> Bar<a href="#Foo-Bar" class="wiki-anchor">&para;</a></h2>'
+ ),
+ result
+ )
+ end
+ end
+
def test_default_formatter
with_settings :text_formatting => 'unknown' do
text = 'a *link*: http://www.example.net/'
@@ -2363,6 +2403,14 @@ class ApplicationHelperTest < Redmine::HelperTest
assert_equal expected, format_activity_description(text)
end
+ def test_render_flash_messages_should_ignore_non_string_values
+ flash[:array_value] = ['1', '2']
+ flash[:hash_value] = { foo: 'bar' }
+
+ result = render_flash_messages
+ assert_equal '', result
+ end
+
private
def wiki_links_with_special_characters
diff --git a/test/helpers/avatars_helper_test.rb b/test/helpers/avatars_helper_test.rb
index f407ae09e..baa64a653 100644
--- a/test/helpers/avatars_helper_test.rb
+++ b/test/helpers/avatars_helper_test.rb
@@ -68,6 +68,18 @@ class AvatarsHelperTest < Redmine::HelperTest
assert_include 'class="gravatar picture"', avatar('jsmith <jsmith@somenet.foo>', :class => 'picture')
end
+ def test_avatar_with_initials
+ with_settings :gravatar_default => 'initials' do
+ assert_include 'initials="RA"', avatar(User.find(1))
+ end
+ end
+
+ def test_avatar_should_reject_initials_if_default_is_not_initials
+ with_settings :gravatar_default => 'identicon' do
+ assert_not_include 'initials="RA"', avatar(User.find(1))
+ end
+ end
+
def test_avatar_disabled
with_settings :gravatar_enabled => '0' do
assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
diff --git a/test/helpers/icons_helper_test.rb b/test/helpers/icons_helper_test.rb
index ab0b58db4..7ef071f86 100644
--- a/test/helpers/icons_helper_test.rb
+++ b/test/helpers/icons_helper_test.rb
@@ -71,6 +71,13 @@ class IconsHelperTest < Redmine::HelperTest
assert_match expected, icon
end
+ def test_sprite_icon_should_return_svg_with_filled_class_when_style_is_filled
+ expected = %r{<svg class="s18 icon-svg icon-svg-filled" aria-hidden="true"><use href="/assets/icons-\w+.svg#icon--edit"></use></svg>$}
+ icon = sprite_icon('edit', style: :filled)
+
+ assert_match expected, icon
+ end
+
def test_file_icon_should_return_folder_icon_for_directory
entry = stub(:is_dir? => true)
expected = %r{<svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-\w+.svg#icon--folder"></use></svg><span class="icon-label">folder_name</span>}
diff --git a/test/helpers/journals_helper_test.rb b/test/helpers/journals_helper_test.rb
index 355d5ec6f..47f7b7749 100644
--- a/test/helpers/journals_helper_test.rb
+++ b/test/helpers/journals_helper_test.rb
@@ -47,10 +47,27 @@ class JournalsHelperTest < Redmine::HelperTest
journals = issue.visible_journals_with_index # add indice
journal_actions = render_journal_actions(issue, journals.first, {reply_links: true})
- assert_select_in journal_actions, 'a[title=?][class="icon icon-comment"]', 'Quote'
+ assert_select_in journal_actions, 'a[title=?][class="icon icon-quote"]', 'Quote'
assert_select_in journal_actions, 'a[title=?][class="icon-only icon-edit"]', 'Edit'
assert_select_in journal_actions, 'div[class="drdn-items"] a[class="icon icon-del"]'
assert_select_in journal_actions, 'div[class="drdn-items"] a[class="icon icon-copy-link"]'
+ assert_select_in journal_actions, 'span.reaction-button-wrapper'
+ end
+
+ def test_render_journal_actions_with_journal_without_notes
+ User.current = User.find(1)
+ issue = Issue.find(1)
+ issue.journals.first.update!(notes: '')
+
+ journals = issue.visible_journals_with_index
+
+ journal_actions = render_journal_actions(issue, journals.first, reply_links: true)
+
+ assert_select_in journal_actions, 'span.reaction-button-wrapper'
+ assert_select_in journal_actions, 'span.drdn'
+
+ assert_select_in journal_actions, 'a[class="icon-comment"]', false
+ assert_select_in journal_actions, 'a[class="icon-edit"]', false
end
def test_journal_thumbnail_attachments_should_be_in_the_same_order_as_the_journal_details
diff --git a/test/helpers/reactions_helper_test.rb b/test/helpers/reactions_helper_test.rb
new file mode 100644
index 000000000..1c5c82418
--- /dev/null
+++ b/test/helpers/reactions_helper_test.rb
@@ -0,0 +1,216 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+
+require_relative '../test_helper'
+
+class ReactionsHelperTest < ActionView::TestCase
+ include ReactionsHelper
+
+ setup do
+ User.current = users(:users_002)
+ Setting.reactions_enabled = '1'
+ end
+
+ teardown do
+ Setting.clear_cache
+ end
+
+ test 'reaction_id_for generates a DOM id' do
+ assert_equal "reaction_issue_1", reaction_id_for(issues(:issues_001))
+ end
+
+ test 'reaction_button returns nil when feature is disabled' do
+ Setting.reactions_enabled = '0'
+
+ assert_nil reaction_button(issues(:issues_004))
+ end
+
+ test 'reaction_button returns nil when object not visible' do
+ User.current = users(:users_003)
+
+ assert_nil reaction_button(issues(:issues_004))
+ end
+
+ test 'reaction_button for anonymous users shows readonly button' do
+ User.current = nil
+
+ result = reaction_button(journals(:journals_001))
+
+ assert_select_in result, 'span.reaction-button.readonly[title=?]', 'John Smith'
+ assert_select_in result, 'a.reaction-button', false
+ end
+
+ test 'reaction_button for inactive projects shows readonly button' do
+ issue6 = issues(:issues_006)
+ issue6.project.update!(status: Project::STATUS_CLOSED)
+
+ result = reaction_button(issue6)
+
+ assert_select_in result, 'span.reaction-button.readonly[title=?]', 'John Smith'
+ assert_select_in result, 'a.reaction-button', false
+ end
+
+ test 'reaction_button includes no tooltip when the object has no reactions' do
+ issue = issues(:issues_002) # Issue without reactions
+ result = reaction_button(issue)
+
+ assert_select_in result, 'a.reaction-button[title]', false
+ end
+
+ test 'reaction_button includes tooltip with all usernames when reactions are 10 or fewer' do
+ issue = issues(:issues_002)
+
+ reactions = build_reactions(10)
+ issue.reactions += reactions
+
+ result = with_locale 'en' do
+ reaction_button(issue)
+ end
+
+ # The tooltip should display usernames in order of newest reactions.
+ expected_tooltip = 'Bob9 Doe, Bob8 Doe, Bob7 Doe, Bob6 Doe, Bob5 Doe, ' \
+ 'Bob4 Doe, Bob3 Doe, Bob2 Doe, Bob1 Doe, and Bob0 Doe'
+
+ assert_select_in result, 'a.reaction-button[title=?]', expected_tooltip
+ end
+
+ test 'reaction_button includes tooltip with 10 usernames and others count when reactions exceed 10' do
+ issue = issues(:issues_002)
+
+ reactions = build_reactions(11)
+ issue.reactions += reactions
+
+ result = with_locale 'en' do
+ reaction_button(issue)
+ end
+
+ expected_tooltip = 'Bob10 Doe, Bob9 Doe, Bob8 Doe, Bob7 Doe, Bob6 Doe, ' \
+ 'Bob5 Doe, Bob4 Doe, Bob3 Doe, Bob2 Doe, Bob1 Doe, and 1 other'
+
+ assert_select_in result, 'a.reaction-button[title=?]', expected_tooltip
+ end
+
+ test 'reaction_button should be label less when no reactions' do
+ issue = issues(:issues_002)
+
+ result = with_locale('en') do
+ reaction_button(issue)
+ end
+ assert_select_in result, 'a.reaction-button' do
+ assert_select 'span.icon-label', false
+ end
+
+ # readonly
+ User.current = nil
+ result = with_locale('en') do
+ reaction_button(issue)
+ end
+ assert_select_in result, 'span.reaction-button.readonly' do
+ assert_select 'span.icon-label', false
+ end
+ end
+
+ test 'reaction_button should not count and display non-visible users' do
+ issue2 = issues(:issues_002)
+
+ issue2.reaction_detail = Reaction::Detail.new(
+ visible_users: users(:users_002, :users_003)
+ )
+
+ result = with_locale('en') do
+ reaction_button(issue2)
+ end
+
+ assert_select_in result, 'a.reaction-button[title=?]', 'John Smith and Dave Lopper'
+
+ # When all users are non-visible users
+ issue2.reaction_detail = Reaction::Detail.new(
+ visible_users: []
+ )
+
+ result = with_locale('en') do
+ reaction_button(issue2)
+ end
+
+ assert_select_in result, 'a.reaction-button[title]', false
+ assert_select_in result, 'a.reaction-button' do
+ assert_select 'span.icon-label', false
+ end
+ end
+
+ test 'reaction_button formats the tooltip content based on the support.array settings of each locale' do
+ result = with_locale('ja') do
+ reaction_button(issues(:issues_001))
+ end
+
+ assert_select_in result, 'a.reaction-button[title=?]', 'Dave Lopper、John Smith、Redmine Admin'
+ end
+
+ test 'reaction_button for reacted object' do
+ User.current = users(:users_002)
+
+ issue = issues(:issues_001)
+
+ result = with_locale('en') do
+ reaction_button(issue)
+ end
+ tooltip = 'Dave Lopper, John Smith, and Redmine Admin'
+
+ assert_select_in result, 'span.reaction-button-wrapper[data-reaction-button-id=?]', 'reaction_issue_1' do
+ href = reaction_path(issue.reaction_detail.user_reaction, object_type: 'Issue', object_id: 1)
+
+ assert_select 'a.icon.reaction-button.reacted[href=?]', href do
+ assert_select 'use[href*=?]', 'thumb-up-filled'
+ assert_select 'span.icon-label', '3'
+ end
+
+ assert_select 'span.reaction-button', false
+ end
+ end
+
+ test 'reaction_button for non-reacted object' do
+ User.current = users(:users_004)
+
+ issue = issues(:issues_001)
+
+ result = with_locale('en') do
+ reaction_button(issue)
+ end
+ tooltip = 'Dave Lopper, John Smith, and Redmine Admin'
+
+ assert_select_in result, 'span.reaction-button-wrapper[data-reaction-button-id=?]', 'reaction_issue_1' do
+ href = reactions_path(object_type: 'Issue', object_id: 1)
+
+ assert_select 'a.icon.reaction-button[href=?]', href do
+ assert_select 'use[href*=?]', 'thumb-up'
+ assert_select 'span.icon-label', '3'
+ end
+
+ assert_select 'span.reaction-button', false
+ end
+ end
+
+ private
+
+ def build_reactions(count)
+ Array.new(count) do |i|
+ Reaction.new(user: User.generate!(firstname: "Bob#{i}"))
+ end
+ end
+end
diff --git a/test/integration/api_test/news_test.rb b/test/integration/api_test/news_test.rb
index bd9f2bb6d..399b2b347 100644
--- a/test/integration/api_test/news_test.rb
+++ b/test/integration/api_test/news_test.rb
@@ -62,7 +62,7 @@ class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base
assert_select "author[id=2][name=\"John Smith\"]"
assert_select 'title', 'eCookbook first release !'
assert_select 'summary', 'First version was released...'
- assert_select 'description', "eCookbook 1.0 has been released.\n\nVisit http://ecookbook.somenet.foo/"
+ assert_select 'description', 'eCookbook 1.0 has been released. Visit http://ecookbook.somenet.foo/'
assert_select 'created_on', News.find(1).created_on.iso8601
end
end
diff --git a/test/system/issues_test.rb b/test/system/issues_test.rb
index 80ef25e0c..c161538e7 100644
--- a/test/system/issues_test.rb
+++ b/test/system/issues_test.rb
@@ -34,6 +34,8 @@ class IssuesSystemTest < ApplicationSystemTestCase
find('input[name=commit]').click
end
+ assert_text /Issue #\d+ created./
+
# find created issue
issue = Issue.find_by_subject("new test issue")
assert_kind_of Issue, issue
@@ -86,6 +88,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
fill_in field2.name, :with => 'CF2 value'
assert_difference 'Issue.count' do
page.first(:button, 'Create').click
+ assert_text /Issue #\d+ created./
end
issue = Issue.order('id desc').first
@@ -125,6 +128,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
end
assert_difference 'Issue.count' do
find('input[name=commit]').click
+ assert_text /Issue #\d+ created./
end
issue = Issue.order('id desc').first
@@ -141,6 +145,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
attach_file 'attachments[dummy][file]', Rails.root.join('test/fixtures/files/testfile.txt')
fill_in 'attachments[1][description]', :with => 'Some description'
click_on 'Create'
+ assert_text /Issue #\d+ created./
end
assert_equal 1, issue.attachments.count
assert_equal 'Some description', issue.attachments.first.description
@@ -163,6 +168,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
attach_file 'attachments[dummy][file]', Rails.root.join('test/fixtures/files/testfile.txt')
fill_in 'attachments[1][description]', :with => 'Some description'
click_on 'Create'
+ assert_text /Issue #\d+ created./
end
assert_equal 1, issue.attachments.count
assert_equal 'Some description', issue.attachments.first.description
@@ -181,6 +187,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
click_on 'Create'
end
click_on 'Create'
+ assert_text /Issue #\d+ created./
end
end
@@ -200,6 +207,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
end
assert_difference 'Issue.count' do
click_button('Create')
+ assert_text /Issue #\d+ created./
end
issue = Issue.order('id desc').first
@@ -230,6 +238,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
fill_in 'Form update CF', :with => 'CF value'
assert_no_difference 'Issue.count' do
page.first(:button, 'Submit').click
+ assert_text 'Successful update.'
end
assert page.has_css?('#flash_notice')
issue = Issue.find(1)
@@ -245,6 +254,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
page.find("#issue_status_id").select("Closed")
assert_no_difference 'Issue.count' do
page.first(:button, 'Submit').click
+ assert_text 'Successful update.'
end
assert page.has_css?('#flash_notice')
assert_equal 5, issue.reload.status.id
@@ -267,6 +277,7 @@ class IssuesSystemTest < ApplicationSystemTestCase
click_on 'Submit'
+ assert_text 'Successful update.'
assert_equal 3, Issue.find(2).attachments.count
end
diff --git a/test/system/oauth_provider_test.rb b/test/system/oauth_provider_test.rb
new file mode 100644
index 000000000..364ed4c94
--- /dev/null
+++ b/test/system/oauth_provider_test.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require_relative '../application_system_test_case'
+require 'oauth2'
+require 'rack'
+require 'puma'
+
+class OauthProviderSystemTest < ApplicationSystemTestCase
+ fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
+ :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
+ :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
+ :watchers, :journals, :journal_details, :versions,
+ :workflows
+
+ test 'application creation and authorization' do
+ #
+ # admin creates the application, granting permissions and generating a uuid
+ # and secret.
+ #
+ log_user 'admin', 'admin'
+ with_settings rest_api_enabled: 1 do
+ visit '/admin'
+ within 'div#admin-menu ul' do
+ click_link 'Applications'
+ end
+ click_link 'New Application'
+ fill_in 'Name', with: 'Oauth Test'
+
+ # as per https://tools.ietf.org/html/rfc8252#section-7.3, the port can be
+ # anything when the redirect URI's host is 127.0.0.1.
+ fill_in 'Redirect URI', with: 'http://127.0.0.1'
+
+ check 'View Issues'
+ click_button 'Create'
+
+ assert_text "Application created."
+ end
+
+ assert app = Doorkeeper::Application.find_by_name('Oauth Test')
+
+ find 'h2', visible: true, text: /Oauth Test/
+ find 'p code', visible: true, text: app.uid
+ find 'p strong', visible: true, text: /will not be shown again/
+ find 'p code', visible: true, text: /View Issues/
+
+ # scrape the clear text secret from the page
+ app_secret = all(:css, 'p code')[1].text
+
+ click_link 'Sign out'
+
+ #
+ # regular user authorizes the application
+ #
+ client = OAuth2::Client.new(app.uid, app_secret, site: "http://127.0.0.1:#{test_port}/")
+
+ # set up a dummy http listener to handle the redirect
+ port = rand 10000..20000
+ redirect_uri = "http://127.0.0.1:#{port}"
+ # the request handler below will set this to the auth token
+ token = nil
+
+ # launches webrick, listening for the redirect with the auth code.
+ launch_client_app(port: port) do |req, res|
+ # get access code from code url param
+ if code = req.params['code'].presence
+ # exchange it for token
+ token = client.auth_code.get_token(code, redirect_uri: redirect_uri)
+ res.body = ["<html><body><p>Authorization succeeded, you may close this window now.</p></body></html>"]
+ end
+ end
+
+ log_user 'jsmith', 'jsmith'
+ with_settings rest_api_enabled: 1 do
+ visit '/my/account'
+ click_link 'Authorized applications'
+ find 'p.nodata', visible: true
+
+ # an oauth client would send the user to this url to request permission
+ url = client.auth_code.authorize_url redirect_uri: redirect_uri, scope: 'view_issues view_project'
+ uri = URI.parse url
+ visit uri.path + '?' + uri.query
+
+ find 'h2', visible: true, text: 'Authorization required'
+ find 'p', visible: true, text: /Authorize Oauth Test/
+ find '.oauth-permissions', visible: true, text: /View Issues/
+ find '.oauth-permissions', visible: true, text: /View project/
+
+ click_button 'Authorize'
+
+ assert grant = app.access_grants.last
+ assert_equal 'view_issues view_project', grant.scopes.to_s
+
+ # check for output defined above in the request handler
+ find 'p', visible: true, text: /Authorization succeeded/
+ assert token.present?
+
+ visit '/my/account'
+ click_link 'Authorized applications'
+ find 'td', visible: true, text: /Oauth Test/
+ click_link 'Sign out'
+
+ # Now, use the token for some API requests
+ assert_raise(RestClient::Unauthorized) do
+ RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json"
+ end
+
+ headers = { 'Authorization' => "Bearer #{token.token}" }
+ r = RestClient.get "http://localhost:#{test_port}/projects/onlinestore/issues.json", headers
+ issues = JSON.parse(r.body)['issues']
+ assert issues.any?
+
+ # time entries access is not part of the granted scopes
+ assert_raise(RestClient::Forbidden) do
+ RestClient.get "http://localhost:#{test_port}/projects/onlinestore/time_entries.json", headers
+ end
+ end
+ end
+
+ private
+
+ def launch_client_app(port: 12345, path: '/', &block)
+ app = ->(env) do
+ req = Rack::Request.new(env)
+ res = Rack::Response.new
+ yield(req, res)
+ res.finish
+ end
+
+ server = Puma::Server.new app
+ server.add_tcp_listener '127.0.0.1', port
+ Thread.new { server.run }
+ end
+
+ def test_port
+ Capybara.current_session.server.port
+ end
+end
diff --git a/test/system/reactions_test.rb b/test/system/reactions_test.rb
new file mode 100644
index 000000000..96dd4cf81
--- /dev/null
+++ b/test/system/reactions_test.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+
+require_relative '../application_system_test_case'
+
+class ReactionsSystemTest < ApplicationSystemTestCase
+ def test_react_to_issue
+ log_user('jsmith', 'jsmith')
+
+ issue = issues(:issues_002)
+
+ with_settings(reactions_enabled: '1') do
+ visit '/issues/2'
+ reaction_button = find("div.issue.details [data-reaction-button-id=\"reaction_issue_#{issue.id}\"]")
+ assert_reaction_add_and_remove(reaction_button, issue)
+ end
+ end
+
+ def test_react_to_journal
+ log_user('jsmith', 'jsmith')
+
+ journal = journals(:journals_002)
+
+ with_settings(reactions_enabled: '1') do
+ visit '/issues/1'
+ reaction_button = find("[data-reaction-button-id=\"reaction_journal_#{journal.id}\"]")
+ assert_reaction_add_and_remove(reaction_button, journal.reload)
+ end
+ end
+
+ def test_react_to_forum_reply
+ log_user('jsmith', 'jsmith')
+
+ reply_message = messages(:messages_002) # reply to message_001
+
+ with_settings(reactions_enabled: '1') do
+ visit 'boards/1/topics/1'
+ reaction_button = find("[data-reaction-button-id=\"reaction_message_#{reply_message.id}\"]")
+ assert_reaction_add_and_remove(reaction_button, reply_message)
+ end
+ end
+
+ def test_react_to_forum_message
+ log_user('jsmith', 'jsmith')
+
+ message = messages(:messages_001)
+
+ with_settings(reactions_enabled: '1') do
+ visit 'boards/1/topics/1'
+ reaction_button = find("[data-reaction-button-id=\"reaction_message_#{message.id}\"]")
+ assert_reaction_add_and_remove(reaction_button, message)
+ end
+ end
+
+ def test_react_to_news
+ log_user('jsmith', 'jsmith')
+
+ with_settings(reactions_enabled: '1') do
+ visit '/news/2'
+ reaction_button = find("[data-reaction-button-id=\"reaction_news_2\"]")
+ assert_reaction_add_and_remove(reaction_button, news(:news_002))
+ end
+ end
+
+ def test_react_to_comment
+ log_user('jsmith', 'jsmith')
+
+ comment = comments(:comments_002)
+
+ with_settings(reactions_enabled: '1') do
+ visit '/news/1'
+ reaction_button = find("[data-reaction-button-id=\"reaction_comment_#{comment.id}\"]")
+ assert_reaction_add_and_remove(reaction_button, comment)
+ end
+ end
+
+ def test_reactions_disabled
+ log_user('jsmith', 'jsmith')
+
+ with_settings(reactions_enabled: '0') do
+ visit '/issues/1'
+ assert_no_selector('[data-reaction-button-id="reaction_issue_1"]')
+ end
+ end
+
+ def test_reaction_button_is_visible_but_not_clickable_for_not_logged_in_user
+ with_settings(reactions_enabled: '1') do
+ visit '/issues/1'
+
+ # visible
+ reaction_button = find('div.issue.details [data-reaction-button-id="reaction_issue_1"]')
+ within(reaction_button) { assert_selector('span.reaction-button') }
+ assert_equal "3", reaction_button.text
+
+ # not clickable
+ within(reaction_button) { assert_no_selector('a.reaction-button') }
+ end
+ end
+
+ def test_reaction_button_is_visible_on_property_changes_tab
+ # Create a journal with no notes
+ journal_without_notes = Journal.generate!(journalized: issues(:issues_001), notes: '', details: [JournalDetail.new])
+
+ log_user('jsmith', 'jsmith')
+
+ visit '/issues/1?tab=properties'
+
+ # Scroll to the history content
+ click_link '#1'
+
+ assert_selector '#tab-properties.selected'
+
+ within('#change-1') do
+ assert_selector 'a.reaction-button'
+
+ assert_no_selector 'a.icon-quote'
+ assert_no_selector 'span.drdn'
+ end
+ within("#change-#{journal_without_notes.id}") do
+ assert_selector 'a.reaction-button'
+
+ assert_no_selector '.drdn'
+ end
+
+ click_link 'History'
+
+ within('#change-1') do
+ assert_selector 'a.reaction-button'
+
+ assert_selector 'a.icon-quote'
+ assert_selector 'span.drdn'
+ end
+ within("#change-#{journal_without_notes.id}") do
+ assert_selector 'a.reaction-button'
+ assert_selector 'span.drdn'
+
+ assert_no_selector 'a.icon-quote'
+ end
+ end
+
+ private
+
+ def assert_reaction_add_and_remove(reaction_button, expected_subject)
+ # Add a reaction
+ within(reaction_button) { find('a.reaction-button').click }
+ find('body').hover # Hide tooltip
+ within(reaction_button) { assert_selector('a.reaction-button.reacted[title="John Smith"]') }
+ assert_equal "1", reaction_button.text
+ assert_equal 1, expected_subject.reactions.count
+
+ # Remove the reaction
+ within(reaction_button) { find('a.reacted').click }
+ within(reaction_button) { assert_selector('a.reaction-button:not(.reacted)') }
+ assert_equal "", reaction_button.text
+ assert_equal 0, expected_subject.reactions.count
+ end
+end
diff --git a/test/system/sudo_mode_test.rb b/test/system/sudo_mode_test.rb
index 73e755acd..307d465ff 100644
--- a/test/system/sudo_mode_test.rb
+++ b/test/system/sudo_mode_test.rb
@@ -48,7 +48,6 @@ class SudoModeSystemTest < ApplicationSystemTestCase
find('input[name=commit]').click
end
- assert_equal '/users', current_path
assert page.has_content?("Confirm your password to continue")
assert page.has_css?('form#sudo-form')
@@ -56,6 +55,8 @@ class SudoModeSystemTest < ApplicationSystemTestCase
fill_in 'Password', :with => 'admin'
click_button 'Submit'
end
+
+ assert_text /User johnpaul created./
end
end
diff --git a/test/system/timelog_test.rb b/test/system/timelog_test.rb
index 57c521096..38c3ae19c 100644
--- a/test/system/timelog_test.rb
+++ b/test/system/timelog_test.rb
@@ -49,6 +49,8 @@ class TimelogTest < ApplicationSystemTestCase
select 'QA', :from => 'Activity'
page.first(:button, 'Submit').click
+ assert_text 'Successful update.'
+
entries = TimeEntry.where(:id => [1, 2, 3]).to_a
assert entries.all? {|entry| entry.hours == 8.5}
assert entries.all? {|entry| entry.activity.name == 'QA'}
@@ -89,6 +91,7 @@ class TimelogTest < ApplicationSystemTestCase
select 'Tracker', :from => 'Available Columns'
page.first('input[type=button].move-right').click
click_on 'Save'
+ assert_text 'Successful update.'
# Display the list with updated settings
visit '/time_entries'
diff --git a/test/unit/lib/redmine/field_format/progressbar_format_test.rb b/test/unit/lib/redmine/field_format/progressbar_format_test.rb
index 51bd8bc5f..6e0df724d 100644
--- a/test/unit/lib/redmine/field_format/progressbar_format_test.rb
+++ b/test/unit/lib/redmine/field_format/progressbar_format_test.rb
@@ -116,5 +116,17 @@ module Redmine::FieldFormat
end
end
end
+
+ def test_formatted_value_with_html_true
+ expected = progress_bar(50)
+ formatted = @format.formatted_value(self, @field, 50, Issue.new, true)
+ assert_equal expected, formatted
+ assert formatted.html_safe?
+ end
+
+ def test_formatted_value_with_html_false
+ formatted = @format.formatted_value(self, @field, 50, Issue.new, false)
+ assert_equal '50', formatted
+ end
end
end
diff --git a/test/unit/lib/redmine/quote_reply_helper_test.rb b/test/unit/lib/redmine/quote_reply_helper_test.rb
index 43adb521b..cbac1f6d0 100644
--- a/test/unit/lib/redmine/quote_reply_helper_test.rb
+++ b/test/unit/lib/redmine/quote_reply_helper_test.rb
@@ -29,7 +29,7 @@ class QuoteReplyHelperTest < ActionView::TestCase
a_tag = quote_reply(url, '#issue_description_wiki')
assert_includes a_tag, %|onclick="#{h "quoteReply('/issues/1/quoted', '#issue_description_wiki', 'common_mark'); return false;"}"|
- assert_includes a_tag, %|class="icon icon-comment"|
+ assert_includes a_tag, %|class="icon icon-quote"|
assert_not_includes a_tag, 'title='
# When icon_only is true
diff --git a/test/unit/lib/redmine/reaction_test.rb b/test/unit/lib/redmine/reaction_test.rb
new file mode 100644
index 000000000..f3228a3bd
--- /dev/null
+++ b/test/unit/lib/redmine/reaction_test.rb
@@ -0,0 +1,189 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+
+require_relative '../../../test_helper'
+
+class Redmine::ReactionTest < ActiveSupport::TestCase
+ setup do
+ @user = users(:users_002)
+ @issue = issues(:issues_007)
+ Setting.reactions_enabled = '1'
+ end
+
+ teardown do
+ Setting.clear_cache
+ end
+
+ test 'preload_reaction_details preloads ReactionDetail for all objects in the collection' do
+ User.current = users(:users_002)
+
+ issue1 = issues(:issues_001)
+ issue2 = issues(:issues_002)
+
+ assert_nil issue1.instance_variable_get(:@reaction_detail)
+ assert_nil issue2.instance_variable_get(:@reaction_detail)
+
+ Issue.preload_reaction_details([issue1, issue2])
+
+ expected_issue1_reaction_detail = Reaction::Detail.new(
+ visible_users: [users(:users_003), users(:users_002), users(:users_001)],
+ user_reaction: reactions(:reaction_002)
+ )
+
+ # ReactionDetail is already preloaded, so calling reaction_detail does not execute any query.
+ assert_no_queries do
+ assert_equal expected_issue1_reaction_detail, issue1.reaction_detail
+
+ # Even when an object has no reactions, an empty ReactionDetail is set.
+ assert_equal Reaction::Detail.new(
+ visible_users: [],
+ user_reaction: nil
+ ), issue2.reaction_detail
+ end
+ end
+
+ test 'visible_users in ReactionDetail preloaded by preload_reaction_details does not include non-visible users' do
+ current_user = User.current = User.generate!
+ visible_user = users(:users_002)
+ non_visible_user = User.generate!
+
+ project = Project.generate!
+ role = Role.generate!(users_visibility: 'members_of_visible_projects')
+
+ User.add_to_project(current_user, project, role)
+ User.add_to_project(visible_user, project, roles(:roles_001))
+
+ issue = Issue.generate!(project: project)
+
+ [current_user, visible_user, non_visible_user].each do |user|
+ issue.reactions.create!(user: user)
+ end
+
+ Issue.preload_reaction_details([issue])
+
+ # non_visible_user is not visible to current_user because they do not belong to any project.
+ assert_equal [visible_user, current_user], issue.reaction_detail.visible_users
+ end
+
+ test 'preload_reaction_details does nothing when the reaction feature is disabled' do
+ Setting.reactions_enabled = '0'
+
+ User.current = users(:users_002)
+ news1 = news(:news_001)
+
+ # Stub the Setting to avoid executing queries for retrieving settings,
+ # making it easier to confirm no queries are executed by preload_reaction_details().
+ Setting.stubs(:reactions_enabled?).returns(false)
+
+ assert_no_queries do
+ News.preload_reaction_details([news1])
+ end
+
+ assert_nil news1.instance_variable_get(:@reaction_detail)
+ end
+
+ test 'reaction_detail loads and returns ReactionDetail if it is not preloaded' do
+ message7 = messages(:messages_007)
+
+ User.current = users(:users_002)
+ assert_nil message7.instance_variable_get(:@reaction_detail)
+
+ assert_equal Reaction::Detail.new(
+ visible_users: [users(:users_002)],
+ user_reaction: reactions(:reaction_009)
+ ), message7.reaction_detail
+ end
+
+ test 'load_reaction_detail loads ReactionDetail for the object itself' do
+ comment1 = comments(:comments_001)
+
+ User.current = users(:users_001)
+ assert_nil comment1.instance_variable_get(:@reaction_detail)
+
+ comment1.load_reaction_detail
+
+ assert_equal Reaction::Detail.new(
+ visible_users: [users(:users_002)],
+ user_reaction: nil
+ ), comment1.reaction_detail
+ end
+
+ test 'visible? returns true when reactions are enabled and object is visible to user' do
+ object = issues(:issues_007)
+ user = users(:users_002)
+
+ assert Redmine::Reaction.visible?(object, user)
+ end
+
+ test 'visible? returns false when reactions are disabled' do
+ Setting.reactions_enabled = '0'
+
+ object = issues(:issues_007)
+ user = users(:users_002)
+
+ assert_not Redmine::Reaction.visible?(object, user)
+ end
+
+ test 'visible? returns false when object is not visible to user' do
+ object = issues(:issues_007)
+ user = users(:users_002)
+
+ object.expects(:visible?).with(user).returns(false)
+
+ assert_not Redmine::Reaction.visible?(object, user)
+ end
+
+ test 'editable? returns true for various reactable objects when user is logged in, object is visible, and project is active' do
+ reactable_objects = {
+ issue: issues(:issues_007),
+ message: messages(:messages_001),
+ news: news(:news_001),
+ journal: journals(:journals_001),
+ comment: comments(:comments_002)
+ }
+ user = users(:users_002)
+
+ reactable_objects.each do |type, object|
+ assert Redmine::Reaction.editable?(object, user), "Expected editable? to return true for #{type}"
+ end
+ end
+
+ test 'editable? returns false when user is not logged in' do
+ object = issues(:issues_007)
+ user = User.anonymous
+
+ assert_not Redmine::Reaction.editable?(object, user)
+ end
+
+ test 'editable? returns false when project is inactive' do
+ object = issues(:issues_007)
+ user = users(:users_002)
+ object.project.update!(status: Project::STATUS_ARCHIVED)
+
+ assert_not Redmine::Reaction.editable?(object, user)
+ end
+
+ test 'editable? returns false when project is closed' do
+ object = issues(:issues_007)
+ user = users(:users_002)
+ object.project.update!(status: Project::STATUS_CLOSED)
+
+ assert_not Redmine::Reaction.editable?(object, user)
+ end
+end
diff --git a/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb b/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb
index d9a84a2c2..bb0c5d450 100644
--- a/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb
+++ b/test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb
@@ -226,7 +226,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase
text = STR_WITH_PRE.join("\n\n")
replacement = "New text"
- assert_equal [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
+ assert_equal [STR_WITH_PRE[0..1], "New text"].join("\n\n"),
@formatter.new(text).update_section(3, replacement)
end
@@ -319,7 +319,11 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase
html = to_html(text)
%w[note tip warning caution important].each do |alert|
- assert_include "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\">Note</p>\n<p>This is a note.</p>\n</div>", html
+ icon = Redmine::WikiFormatting::CommonMark::ALERT_TYPE_TO_ICON_NAME[alert]
+ # rubocop:disable Layout/LineLength
+ expected = %r{<div class="markdown-alert markdown-alert-#{alert}">\n<p class="markdown-alert-title"><svg class="s18 icon-svg" aria-hidden="true"><use href="/assets/icons-\w+.svg\#icon--#{icon}"></use></svg><span class="icon-label">#{alert.capitalize}</span></p>\n<p>This is a #{alert}.</p>\n</div>}
+ # rubocop:enable Layout/LineLength
+ assert_match expected, html
end
end
diff --git a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
index 32280cfdf..678d4c6b2 100644
--- a/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
+++ b/test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb
@@ -466,19 +466,19 @@ class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
replacement = "New text"
assert_equal(
- [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
+ [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].join("\n\n"),
@formatter.new(TEXT_WITHOUT_PRE).update_section(2, replacement)
)
assert_equal(
- [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"),
+ [STR_WITHOUT_PRE[0..1], replacement, STR_WITHOUT_PRE[4]].join("\n\n"),
@formatter.new(TEXT_WITHOUT_PRE).update_section(3, replacement)
)
assert_equal(
- [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].flatten.join("\n\n"),
+ [STR_WITHOUT_PRE[0..2], replacement, STR_WITHOUT_PRE[4]].join("\n\n"),
@formatter.new(TEXT_WITHOUT_PRE).update_section(5, replacement)
)
assert_equal(
- [STR_WITHOUT_PRE[0..3], replacement].flatten.join("\n\n"),
+ [STR_WITHOUT_PRE[0..3], replacement].join("\n\n"),
@formatter.new(TEXT_WITHOUT_PRE).update_section(6, replacement)
)
assert_equal TEXT_WITHOUT_PRE, @formatter.new(TEXT_WITHOUT_PRE).update_section(0, replacement)
@@ -488,7 +488,7 @@ class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
def test_update_section_with_hash_should_update_the_requested_section
replacement = "New text"
assert_equal(
- [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].flatten.join("\n\n"),
+ [STR_WITHOUT_PRE[0], replacement, STR_WITHOUT_PRE[2..4]].join("\n\n"),
@formatter.new(TEXT_WITHOUT_PRE).
update_section(2, replacement, ActiveSupport::Digest.hexdigest(STR_WITHOUT_PRE[1]))
)
@@ -552,7 +552,7 @@ class Redmine::WikiFormatting::TextileFormatterTest < ActionView::TestCase
text = STR_WITH_PRE.join("\n\n")
replacement = "New text"
assert_equal(
- [STR_WITH_PRE[0..1], "New text"].flatten.join("\n\n"),
+ [STR_WITH_PRE[0..1], "New text"].join("\n\n"),
@formatter.new(text).update_section(3, replacement)
)
end
diff --git a/test/unit/member_test.rb b/test/unit/member_test.rb
index 42fba4783..df9088027 100644
--- a/test/unit/member_test.rb
+++ b/test/unit/member_test.rb
@@ -108,7 +108,7 @@ class MemberTest < ActiveSupport::TestCase
assert !member.save
assert_include I18n.translate('activerecord.errors.messages.empty'), member.errors[:role]
assert_equal 'Rôle doit être renseigné(e)',
- [member.errors.full_messages].flatten.join
+ [member.errors.full_messages].join
end
def test_validate_member_role
diff --git a/test/unit/reaction_test.rb b/test/unit/reaction_test.rb
new file mode 100644
index 000000000..9b3da0738
--- /dev/null
+++ b/test/unit/reaction_test.rb
@@ -0,0 +1,118 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006- Jean-Philippe Lang
+#
+# 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.
+
+require_relative '../test_helper'
+
+class ReactionTest < ActiveSupport::TestCase
+ test 'validates :inclusion of reactable_type' do
+ %w(Issue Journal News Comment Message).each do |type|
+ reaction = Reaction.new(reactable_type: type, user: User.new)
+ assert reaction.valid?
+ end
+
+ assert_not Reaction.new(reactable_type: 'InvalidType', user: User.new).valid?
+ end
+
+ test 'scope: by' do
+ user2_reactions = issues(:issues_001).reactions.by(users(:users_002))
+
+ assert_equal [reactions(:reaction_002)], user2_reactions
+ end
+
+ test "should prevent duplicate reactions with unique constraint under concurrent creation" do
+ user = users(:users_001)
+ issue = issues(:issues_004)
+
+ threads = []
+ results = []
+
+ # Ensure both threads start at the same time
+ barrier = Concurrent::CyclicBarrier.new(2)
+
+ # Create two threads to simulate concurrent creation
+ 2.times do
+ threads << Thread.new do
+ barrier.wait # Wait for both threads to be ready
+ begin
+ reaction = Reaction.create(
+ reactable: issue,
+ user: user
+ )
+ results << reaction.persisted?
+ rescue ActiveRecord::RecordNotUnique
+ results << false
+ end
+ end
+ end
+
+ # Wait for both threads to finish
+ threads.each(&:join)
+
+ # Ensure only one reaction was created
+ assert_equal 1, Reaction.where(reactable: issue, user: user).count
+ assert_includes results, true
+ assert_equal 1, results.count(true)
+ end
+
+ test 'build_detail_map_for generates a detail map for reactable objects' do
+ result = Reaction.build_detail_map_for([issues(:issues_001), issues(:issues_006)], users(:users_003))
+
+ expected = {
+ 1 => Reaction::Detail.new(
+ visible_users: [users(:users_003), users(:users_002), users(:users_001)],
+ user_reaction: reactions(:reaction_003)
+ ),
+ 6 => Reaction::Detail.new(
+ visible_users: [users(:users_002)],
+ user_reaction: nil
+ )
+ }
+ assert_equal expected, result
+
+ # When an object have no reactions, the result should be empty.
+ result = Reaction.build_detail_map_for([journals(:journals_002)], users(:users_002))
+
+ assert_empty result
+ end
+
+ test 'build_detail_map_for filters users based on visibility' do
+ current_user = User.generate!
+ visible_user = users(:users_002)
+ non_visible_user = User.generate!
+
+ project = Project.generate!
+ role = Role.generate!(users_visibility: 'members_of_visible_projects')
+
+ User.add_to_project(current_user, project, role)
+ User.add_to_project(visible_user, project, roles(:roles_001))
+
+ issue = Issue.generate!(project: project)
+
+ [current_user, visible_user, non_visible_user].each do |user|
+ issue.reactions.create!(user: user)
+ end
+
+ result = Reaction.build_detail_map_for([issue], current_user)
+
+ assert_equal(
+ [current_user, visible_user].sort_by(&:id),
+ result[issue.id].visible_users.sort_by(&:id)
+ )
+ end
+end
diff --git a/test/unit/role_test.rb b/test/unit/role_test.rb
index 21103919f..1d0d39d7e 100644
--- a/test/unit/role_test.rb
+++ b/test/unit/role_test.rb
@@ -175,6 +175,32 @@ class RoleTest < ActiveSupport::TestCase
assert_equal false, role.permissions_tracker_ids?(:view_issues, 1)
end
+ def test_allowed_to_with_symbol
+ role = Role.create!(:name => 'Test', :permissions => [:view_issues])
+ assert_equal true, role.allowed_to?(:view_issues)
+ assert_equal false, role.allowed_to?(:add_issues)
+ end
+
+ def test_allowed_to_with_symbol_and_scope
+ role = Role.create!(:name => 'Test', :permissions => [:view_issues, :delete_issues])
+ assert_equal true, role.allowed_to?(:view_issues, [:view_issues, :add_issues])
+ assert_equal false, role.allowed_to?(:add_issues, [:view_issues, :add_issues])
+ assert_equal false, role.allowed_to?(:delete_issues, [:view_issues, :add_issues])
+ end
+
+ def test_allowed_to_with_hash
+ role = Role.create!(:name => 'Test', :permissions => [:view_issues])
+ assert_equal true, role.allowed_to?(:controller => 'issues', :action => 'show')
+ assert_equal false, role.allowed_to?(:controller => 'issues', :action => 'create')
+ end
+
+ def test_allowed_to_with_hash_and_scope
+ role = Role.create!(:name => 'Test', :permissions => [:view_issues, :delete_issues])
+ assert_equal true, role.allowed_to?({:controller => 'issues', :action => 'show'}, [:view_issues, :add_issues])
+ assert_equal false, role.allowed_to?({:controller => 'issues', :action => 'create'}, [:view_issues, :add_issues])
+ assert_equal false, role.allowed_to?({:controller => 'issues', :action => 'destroy'}, [:view_issues, :add_issues])
+ end
+
def test_has_permission_without_permissions
role = Role.create!(:name => 'Test')
assert_equal false, role.has_permission?(:delete_issues)
diff --git a/test/unit/setting_test.rb b/test/unit/setting_test.rb
index 4ae07cebb..cbfabbb02 100644
--- a/test/unit/setting_test.rb
+++ b/test/unit/setting_test.rb
@@ -147,4 +147,8 @@ class SettingTest < ActiveSupport::TestCase
def test_default_text_formatting_for_new_installations_is_common_mark
assert_equal 'common_mark', Setting.text_formatting
end
+
+ def test_default_wiki_tablesort_enabled_for_new_installations_is_disabled
+ assert_equal "0", Setting.wiki_tablesort_enabled
+ end
end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
index ede12e1ce..967771c87 100644
--- a/test/unit/user_test.rb
+++ b/test/unit/user_test.rb
@@ -589,6 +589,27 @@ class UserTest < ActiveSupport::TestCase
end
end
+ def test_initials_format
+ assert_equal 'JS', @jsmith.initials(:firstname_lastinitial)
+ assert_equal 'SJ', @jsmith.initials(:lastname_comma_firstname)
+ assert_equal 'SJ', @jsmith.initials(:lastname_firstname)
+ assert_equal 'JS', @jsmith.initials(:firstinitial_lastname)
+ assert_equal 'JL', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').initials(:firstinitial_lastname)
+ assert_equal 'JS', @jsmith.initials(:undefined_format)
+ end
+
+ def test_initials_should_use_setting_as_default_format
+ with_settings :user_format => :firstname_lastname do
+ assert_equal 'JS', @jsmith.reload.initials
+ end
+ with_settings :user_format => :username do
+ assert_equal 'JS', @jsmith.reload.initials
+ end
+ with_settings :user_format => :lastname do
+ assert_equal 'SM', @jsmith.reload.initials
+ end
+ end
+
def test_lastname_should_accept_255_characters
u = User.first
u.lastname = 'a' * 255
@@ -1376,4 +1397,77 @@ class UserTest < ActiveSupport::TestCase
User.prune(7)
end
end
+
+ def test_should_recognize_authorized_by_oauth
+ u = User.find 2
+ assert_not u.authorized_by_oauth?
+ u.oauth_scope = [:add_issues, :view_issues]
+ assert u.authorized_by_oauth?
+ end
+
+ def test_admin_should_be_limited_by_oauth_scope
+ u = User.find_by_admin(true)
+ assert u.admin?
+
+ u.oauth_scope = [:add_issues, :view_issues]
+ assert_not u.admin?
+
+ u.oauth_scope = [:add_issues, :view_issues, :admin]
+ assert u.admin?
+
+ u = User.find_by_admin(false)
+ assert_not u.admin?
+ u.oauth_scope = [:add_issues, :view_issues, :admin]
+ assert_not u.admin?
+ end
+
+ def test_oauth_scope_should_limit_global_user_permissions
+ admin = User.find 1
+ user = User.find 2
+ [admin, user].each do |u|
+ assert u.allowed_to?(:add_issues, nil, global: true)
+ assert u.allowed_to?(:view_issues, nil, global: true)
+ u.oauth_scope = [:view_issues]
+ assert_not u.allowed_to?(:add_issues, nil, global: true)
+ assert u.allowed_to?(:view_issues, nil, global: true)
+ end
+ end
+
+ def test_oauth_scope_should_limit_project_user_permissions
+ admin = User.find 1
+ project = Project.find 5
+ assert admin.allowed_to?(:add_issues, project)
+ assert admin.allowed_to?(:view_issues, project)
+ admin.oauth_scope = [:view_issues]
+ assert_not admin.allowed_to?(:add_issues, project)
+ assert admin.allowed_to?(:view_issues, project)
+
+ admin.oauth_scope = [:view_issues, :admin]
+ assert admin.allowed_to?(:add_issues, project)
+ assert admin.allowed_to?(:view_issues, project)
+
+ user = User.find 2
+ project = Project.find 1
+ assert user.allowed_to?(:add_issues, project)
+ assert user.allowed_to?(:view_issues, project)
+ user.oauth_scope = [:view_issues]
+ assert_not user.allowed_to?(:add_issues, project)
+ assert user.allowed_to?(:view_issues, project)
+
+ user.oauth_scope = [:view_issues, :admin]
+ assert_not user.allowed_to?(:add_issues, project)
+ assert user.allowed_to?(:view_issues, project)
+ end
+
+ def test_destroy_should_delete_associated_reactions
+ users(:users_004).reactions.create!(
+ [
+ {reactable: issues(:issues_001)},
+ {reactable: issues(:issues_002)}
+ ]
+ )
+ assert_difference 'Reaction.count', -2 do
+ users(:users_004).destroy
+ end
+ end
end