diff options
Diffstat (limited to 'test/unit')
-rw-r--r-- | test/unit/lib/redmine/field_format/progressbar_format_test.rb | 12 | ||||
-rw-r--r-- | test/unit/lib/redmine/quote_reply_helper_test.rb | 2 | ||||
-rw-r--r-- | test/unit/lib/redmine/reaction_test.rb | 189 | ||||
-rw-r--r-- | test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb | 8 | ||||
-rw-r--r-- | test/unit/lib/redmine/wiki_formatting/textile_formatter_test.rb | 12 | ||||
-rw-r--r-- | test/unit/member_test.rb | 2 | ||||
-rw-r--r-- | test/unit/reaction_test.rb | 118 | ||||
-rw-r--r-- | test/unit/role_test.rb | 26 | ||||
-rw-r--r-- | test/unit/setting_test.rb | 4 | ||||
-rw-r--r-- | test/unit/user_test.rb | 94 |
10 files changed, 457 insertions, 10 deletions
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 |