diff options
Diffstat (limited to 'test/unit')
33 files changed, 991 insertions, 125 deletions
diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb index ca1e010e3..3ad8b1cbf 100644 --- a/test/unit/changeset_test.rb +++ b/test/unit/changeset_test.rb @@ -479,7 +479,7 @@ class ChangesetTest < ActiveSupport::TestCase end def test_next_nil - changeset = Changeset.find_by_revision('10') + changeset = Changeset.find_by_revision('11') assert_nil changeset.next end diff --git a/test/unit/email_address_test.rb b/test/unit/email_address_test.rb index 9d57beb97..923df897a 100644 --- a/test/unit/email_address_test.rb +++ b/test/unit/email_address_test.rb @@ -63,6 +63,12 @@ class EmailAddressTest < ActiveSupport::TestCase end end + def test_domain_in_should_not_raise_exception_when_domain_is_nil + assert_nothing_raised do + assert_not EmailAddress.domain_in?(nil, 'example.com') + end + end + def test_should_reject_invalid_email assert_not EmailAddress.new(address: 'invalid,email@example.com').valid? end diff --git a/test/unit/issue_priority_test.rb b/test/unit/issue_priority_test.rb index e076afe67..80dc11e1c 100644 --- a/test/unit/issue_priority_test.rb +++ b/test/unit/issue_priority_test.rb @@ -156,4 +156,20 @@ class IssuePriorityTest < ActiveSupport::TestCase IssuePriority.find_by_position_name('highest').destroy assert_equal %w(lowest default high2 highest), IssuePriority.active.to_a.sort.map(&:position_name) end + + def test_high_should_return_false_when_no_default_priority_and_no_active_priorities + IssuePriority.update_all(active: false, is_default: false) + priority = IssuePriority.order(:position).last # Highest priority + assert_nothing_raised do + assert_equal false, priority.high? + end + end + + def test_low_should_return_false_when_no_default_priority_and_no_active_priorities + IssuePriority.update_all(active: false, is_default: false) + priority = IssuePriority.order(:position).first # Lowest priority + assert_nothing_raised do + assert_equal false, priority.low? + end + end end diff --git a/test/unit/lib/redmine/field_format/numeric_format_test.rb b/test/unit/lib/redmine/field_format/numeric_format_test.rb index 2c9ecdc2e..7e5194d9a 100644 --- a/test/unit/lib/redmine/field_format/numeric_format_test.rb +++ b/test/unit/lib/redmine/field_format/numeric_format_test.rb @@ -33,13 +33,21 @@ class Redmine::NumericFieldFormatTest < ActionView::TestCase assert_equal '<a href="http://foo/3" class="external">3</a>', field.format.formatted_custom_value(self, custom_value, true) end - def test_float_field_value_should_validate_when_given_with_various_separator + def test_float_field_should_normalize_decimal_separator field = IssueCustomField.generate!(field_format: 'float') issue = Issue.generate!(tracker: Tracker.find(1), status: IssueStatus.find(1), priority: IssuePriority.find(6)) - to_test = {'en' => '3.33', 'de' => '3,33'} - to_test.each do |locale, expected| - with_locale locale do - assert field.format.validate_single_value(field, expected, issue) + + with_locale 'de' do + issue.custom_field_values = { field.id => '3,33' } + assert issue.save! + assert_equal '3.33', issue.reload.custom_field_values.last.value + end + + # Comma decimal separator is not allowed in English locale + with_locale 'en' do + issue.custom_field_values = { field.id => '3,33' } + assert_raise ActiveRecord::RecordInvalid do + issue.save! end end end diff --git a/test/unit/lib/redmine/field_format/progressbar_format_test.rb b/test/unit/lib/redmine/field_format/progressbar_format_test.rb new file mode 100644 index 000000000..6e0df724d --- /dev/null +++ b/test/unit/lib/redmine/field_format/progressbar_format_test.rb @@ -0,0 +1,132 @@ +# 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' +require 'redmine/field_format' + +module Redmine::FieldFormat + class ProgressbarFormatTest < ActionView::TestCase + def setup + @field = IssueCustomField.new(name: 'ProgressbarTest', field_format: 'progressbar') + @format = Redmine::FieldFormat::ProgressbarFormat.instance + end + + def test_validate_invalid_value + cv = CustomValue.new(custom_field: @field, value: '120') + assert_include ::I18n.t('activerecord.errors.messages.invalid'), @format.validate_custom_value(cv) + end + + def test_validate_numericality + cv = CustomValue.new(custom_field: @field, value: 'abc') + assert_include ::I18n.t('activerecord.errors.messages.not_a_number'), @format.validate_custom_value(cv) + end + + def test_cast_value_clamping + assert_equal 0, @field.cast_value('-10') + assert_equal 0, @field.cast_value('0') + assert_equal 50, @field.cast_value('50') + assert_equal 100, @field.cast_value('120') + end + + def test_empty_value + assert_nil @field.cast_value('') + end + + def test_totalable_support + assert_not @format.totalable_supported? + end + + def test_validate_non_numeric_value_should_fail + assert_include ::I18n.t('activerecord.errors.messages.not_a_number'), + @format.validate_single_value(@field, "abc") + end + + def test_validate_negative_value_should_fail + assert_include ::I18n.t('activerecord.errors.messages.invalid'), + @format.validate_single_value(@field, "-10") + end + + def test_validate_value_above_100_should_fail + assert_include ::I18n.t('activerecord.errors.messages.invalid'), + @format.validate_single_value(@field, "150") + end + + def test_validate_valid_value_should_pass + assert_empty @format.validate_single_value(@field, "50") + assert_empty @format.validate_single_value(@field, "0") + assert_empty @format.validate_single_value(@field, "100") + end + + def test_validate_blank_value_should_pass + assert_empty @format.validate_single_value(@field, "") + end + + def test_query_filter_options + options = @format.query_filter_options(@field, nil) + assert_equal :integer, options[:type] + end + + def test_default_ratio_interval_should_be_default_issue_done_ratio_interval + @field.save + assert_equal 10, @field.ratio_interval + end + + def test_ratio_interval + @field.update(ratio_interval: 5) + assert_equal 5, @field.ratio_interval + end + + def test_edit_tag_possible_values_with_ratio_interval + [5, 10].each do |ratio_interval| + @field.update(ratio_interval: ratio_interval) + value = CustomValue.new(custom_field: @field, value: '90') + + tag = @field.format.edit_tag(self, 'id', 'name', value) + assert_select_in tag, 'select' do + assert_select 'option', 100 / ratio_interval + 1 + end + end + end + + def test_bulk_edit_tag_possible_values_with_ratio_interval + [5, 10].each do |ratio_interval| + @field.update(ratio_interval: ratio_interval) + value = CustomValue.new(custom_field: @field, value: '90') + objects = [Issue.new, Issue.new] + + tag = @field.format.bulk_edit_tag(self, 'id', 'name', @field, objects, value) + assert_select_in tag, 'select' do |select| + assert_select select.first, 'option', 100 / ratio_interval + 2 + 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..d5d13d4f8 100644 --- a/test/unit/lib/redmine/quote_reply_helper_test.rb +++ b/test/unit/lib/redmine/quote_reply_helper_test.rb @@ -23,18 +23,18 @@ class QuoteReplyHelperTest < ActionView::TestCase include ERB::Util include Redmine::QuoteReply::Helper - def test_quote_reply + def test_quote_reply_button with_locale 'en' do url = quoted_issue_path(issues(:issues_001)) - 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_not_includes a_tag, 'title=' + html = quote_reply_button(url: url) + assert_select_in html, + 'a[data-quote-reply-url-param=?][data-quote-reply-text-formatting-param=?]:not([title])', + url, Setting.text_formatting # When icon_only is true - a_tag = quote_reply(url, '#issue_description_wiki', icon_only: true) - assert_includes a_tag, %|title="Quote"| + html = quote_reply_button(url: url, icon_only: true) + assert_select_in html, 'a.icon-only.icon-quote[title=?]', 'Quote' end end end 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/scm/adapters/bazaar_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb index c0bff9b1f..9d6cd6b32 100644 --- a/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/bazaar_adapter_test.rb @@ -27,6 +27,7 @@ class BazaarAdapterTest < ActiveSupport::TestCase def setup @adapter = Redmine::Scm::Adapters::BazaarAdapter. new(File.join(REPOSITORY_PATH, "trunk")) + skip "SCM command is unavailable" unless @adapter.class.client_available end def test_scm_version diff --git a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb index 2ed9dc618..3bfe24997 100644 --- a/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/cvs_adapter_test.rb @@ -27,6 +27,7 @@ class CvsAdapterTest < ActiveSupport::TestCase if File.directory?(REPOSITORY_PATH) def setup @adapter = Redmine::Scm::Adapters::CvsAdapter.new(MODULE_NAME, REPOSITORY_PATH) + skip "SCM command is unavailable" unless @adapter.class.client_available end def test_scm_version diff --git a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb index bf054860a..3f0451601 100644 --- a/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/git_adapter_test.rb @@ -42,13 +42,6 @@ class GitAdapterTest < ActiveSupport::TestCase WINDOWS_SKIP_STR = "TODO: This test fails in Git for Windows above 1.7.10" def setup - adapter_class = Redmine::Scm::Adapters::GitAdapter - assert adapter_class - assert adapter_class.client_command - assert_equal true, adapter_class.client_available - assert_equal true, adapter_class.client_version_above?([1]) - assert_equal true, adapter_class.client_version_above?([1, 0]) - @adapter = Redmine::Scm::Adapters::GitAdapter. new( @@ -59,6 +52,8 @@ class GitAdapterTest < ActiveSupport::TestCase 'ISO-8859-1' ) assert @adapter + skip "SCM is unavailable" unless @adapter.class.client_available + @char_1 = 'Ü' @str_felix_hex = "Felix Sch\xC3\xA4fer".b end diff --git a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb index b4f284103..81741a746 100644 --- a/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/mercurial_adapter_test.rb @@ -30,12 +30,6 @@ class MercurialAdapterTest < ActiveSupport::TestCase if File.directory?(REPOSITORY_PATH) def setup - adapter_class = Redmine::Scm::Adapters::MercurialAdapter - assert adapter_class - assert adapter_class.client_command - assert_equal true, adapter_class.client_available - assert_equal true, adapter_class.client_version_above?([0, 9, 5]) - @adapter = Redmine::Scm::Adapters::MercurialAdapter.new( REPOSITORY_PATH, @@ -44,6 +38,8 @@ class MercurialAdapterTest < ActiveSupport::TestCase nil, 'ISO-8859-1' ) + skip "SCM command is unavailable" unless @adapter.class.client_available + @diff_c_support = true @char_1 = 'Ü' @tag_char_1 = 'tag-Ü-00' diff --git a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb index fe574a4ff..edc3541d1 100644 --- a/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb +++ b/test/unit/lib/redmine/scm/adapters/subversion_adapter_test.rb @@ -23,6 +23,7 @@ class SubversionAdapterTest < ActiveSupport::TestCase if repository_configured?('subversion') def setup @adapter = Redmine::Scm::Adapters::SubversionAdapter.new(self.class.subversion_repository_url) + skip "SCM command is unavailable" unless @adapter.class.client_available end def test_client_version 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 5214a1e00..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 @@ -26,71 +26,71 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase @formatter = Redmine::WikiFormatting::CommonMark::Formatter end - def format(text) + def to_html(text) @formatter.new(text).to_html end def test_should_render_hard_breaks html ="<p>foo<br>\nbar</p>" - assert_equal html, format("foo\\\nbar") - assert_equal html, format("foo \nbar") + assert_equal html, to_html("foo\\\nbar") + assert_equal html, to_html("foo \nbar") end def test_should_render_soft_breaks - assert_equal "<p>foo<br>\nbar</p>", format("foo\nbar") + assert_equal "<p>foo<br>\nbar</p>", to_html("foo\nbar") end def test_syntax_error_in_image_reference_should_not_raise_exception - assert format("!>[](foo.png)") + assert to_html("!>[](foo.png)") end def test_empty_image_should_not_raise_exception - assert format("![]()") + assert to_html("![]()") end def test_inline_style - assert_equal "<p><strong>foo</strong></p>", format("**foo**") + assert_equal "<p><strong>foo</strong></p>", to_html("**foo**") end def test_not_set_intra_emphasis - assert_equal "<p>foo_bar_baz</p>", format("foo_bar_baz") + assert_equal "<p>foo_bar_baz</p>", to_html("foo_bar_baz") end def test_wiki_links_should_be_preserved text = 'This is a wiki link: [[Foo]]' - assert_include '[[Foo]]', format(text) + assert_include '[[Foo]]', to_html(text) end def test_redmine_links_with_double_quotes_should_be_preserved text = 'This is a redmine link: version:"1.0"' - assert_include 'version:"1.0"', format(text) + assert_include 'version:"1.0"', to_html(text) end def test_links_by_id_should_be_preserved text = "[project#3]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) end def test_links_to_users_should_be_preserved text = "[@login]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[user:login]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "user:user@example.org" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[user:user@example.org]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "@user@example.org" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[@user@example.org]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) end def test_files_with_at_should_not_end_up_as_mailto_links text = "printscreen@2x.png" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) text = "[printscreen@2x.png]" - assert_equal "<p>#{text}</p>", format(text) + assert_equal "<p>#{text}</p>", to_html(text) end def test_should_support_syntax_highlight @@ -100,7 +100,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase end ~~~ STR - assert_select_in format(text), 'pre code.ruby.syntaxhl' do + assert_select_in to_html(text), 'pre code.ruby.syntaxhl' do assert_select 'span.k', :text => 'def' assert_select "[data-language='ruby']" end @@ -114,7 +114,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase ~~~ STR - assert_select_in format(text), 'pre' do + assert_select_in to_html(text), 'pre' do assert_select 'code[class=?]', "c++ syntaxhl" assert_select 'span.kt', :text => 'int' assert_select "[data-language=?]", "c++" @@ -123,12 +123,12 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase def test_external_links_should_have_external_css_class text = 'This is a [link](http://example.net/)' - assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', format(text) + assert_equal '<p>This is a <a href="http://example.net/" class="external">link</a></p>', to_html(text) end def test_locals_links_should_not_have_external_css_class text = 'This is a [link](/issues)' - assert_equal '<p>This is a <a href="/issues">link</a></p>', format(text) + assert_equal '<p>This is a <a href="/issues">link</a></p>', to_html(text) end def test_markdown_should_not_require_surrounded_empty_line @@ -137,7 +137,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase * One * Two STR - assert_equal "<p>This is a list:</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", format(text) + assert_equal "<p>This is a list:</p>\n<ul>\n<li>One</li>\n<li>Two</li>\n</ul>", to_html(text) end def test_footnotes @@ -156,46 +156,46 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase </ol> EXPECTED - assert_equal expected.gsub(%r{[\r\n\t]}, ''), format(text).gsub(%r{[\r\n\t]}, '').rstrip + assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip end STR_WITH_PRE = [ # 0 <<~STR.chomp, # Title - + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero. STR # 1 <<~STR.chomp, ## Heading 2 - + ~~~ruby def foo end ~~~ - + Morbi facilisis accumsan orci non pharetra. - + ~~~ ruby def foo end ~~~ - + ``` Pre Content: - + ## Inside pre - + <tag> inside pre block - + Morbi facilisis accumsan orci non pharetra. ``` STR # 2 <<~STR.chomp, ### Heading 3 - + Nulla nunc nisi, egestas in ornare vel, posuere ac libero. STR ] @@ -226,18 +226,18 @@ 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 def test_should_emphasize_text text = 'This _text_ should be emphasized' - assert_equal '<p>This <em>text</em> should be emphasized</p>', format(text) + assert_equal '<p>This <em>text</em> should be emphasized</p>', to_html(text) end def test_should_strike_through_text text = 'This ~~text~~ should be striked through' - assert_equal '<p>This <del>text</del> should be striked through</p>', format(text) + assert_equal '<p>This <del>text</del> should be striked through</p>', to_html(text) end def test_should_autolink_urls_and_emails @@ -249,13 +249,13 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase ["www.example.org", '<p><a href="http://www.example.org" class="external">www.example.org</a></p>'], ["user@example.org", '<p><a href="mailto:user@example.org" class="email">user@example.org</a></p>'] ].each do |text, html| - assert_equal html, format(text) + assert_equal html, to_html(text) end end def test_should_support_html_tables text = '<table style="background: red"><tr><td>Cell</td></tr></table>' - assert_equal '<table><tr><td>Cell</td></tr></table>', format(text) + assert_equal '<table><tr><td>Cell</td></tr></table>', to_html(text) end def test_should_remove_unsafe_uris @@ -263,7 +263,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase ['<img src="data:foobar">', '<img>'], ['<a href="javascript:bla">click me</a>', '<p><a>click me</a></p>'], ].each do |text, html| - assert_equal html, format(text) + assert_equal html, to_html(text) end end @@ -274,7 +274,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase %[sit<br/>amet <style>.foo { color: #fff; }</style> <script>alert("hello world");</script>] ] ].each do |expected, input| - assert_equal expected, format(input) + assert_equal expected, to_html(input) end end @@ -287,7 +287,7 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase expected = <<~EXPECTED <p>Task list:</p> - <ul class="task-list"> + <ul class="contains-task-list"> <li class="task-list-item"> <input type="checkbox" class="task-list-item-checkbox" disabled> Task 1 </li> @@ -296,7 +296,50 @@ class Redmine::WikiFormatting::CommonMark::FormatterTest < ActionView::TestCase </ul> EXPECTED - assert_equal expected.gsub(%r{[\r\n\t]}, ''), format(text).gsub(%r{[\r\n\t]}, '').rstrip + assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip + end + + def test_should_render_alert_blocks + text = <<~MD + > [!note] + > This is a note. + + > [!tip] + > This is a tip. + + > [!warning] + > This is a warning. + + > [!caution] + > This is a caution. + + > [!important] + > This is a important. + MD + + html = to_html(text) + %w[note tip warning caution important].each do |alert| + 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 + + def test_should_not_render_unknown_alert_type + text = <<~MD + > [!unknown] + > This should not become an alert. + MD + + html = to_html(text) + + assert_include "<blockquote>", html + assert_include "[!unknown]", html + assert_include "This should not become an alert.", html + + assert_not_include 'markdown-alert', html end private diff --git a/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb b/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb index 4c0282f2d..b2d19eab9 100644 --- a/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb @@ -47,10 +47,14 @@ if Object.const_defined?(:Commonmarker) end def test_should_support_footnotes - input = %(<a href="#fn-1" id="fnref-1">foo</a>) - assert_equal input, filter(input) - input = %(<ol><li id="fn-1">footnote</li></ol>) - assert_equal input, filter(input) + [ + %(<a href="#fn-1" id="fnref-1">foo</a>), + %(<a href="#fn-1" id="fnref-1-2">foo</a>), + %(<ol><li id="fn-1">footnote</li></ol>), + ].each do |input| + assert_equal input, filter(input) + assert_equal input, filter(input) + end end def test_should_remove_invalid_ids @@ -71,6 +75,32 @@ if Object.const_defined?(:Commonmarker) assert_equal %(<code>foo</code>), filter(input) end + def test_should_allow_valid_alert_div_and_p_classes + html = <<~HTML + <div class="markdown-alert markdown-alert-tip"> + <p class="markdown-alert-title">Tip</p> + <p>Useful tip.</p> + </div> + HTML + + sanitized = filter(html) + + assert_include 'class="markdown-alert markdown-alert-tip"', sanitized + assert_include 'class="markdown-alert-title"', sanitized + end + + def test_should_remove_invalid_div_class + html = '<div class="bad-class">Text</div>' + sanitized = filter(html) + assert_not_includes 'bad-class', sanitized + end + + def test_should_remove_invalid_p_class + html = '<p class="bad-class">Text</p>' + sanitized = filter(html) + assert_not_include 'bad-class', sanitized + end + def test_should_allow_links_with_safe_url_schemes %w(http https ftp ssh foo).each do |scheme| input = %(<a href="#{scheme}://example.org/">foo</a>) diff --git a/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb b/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb index 11dddb5f8..f8793cf9f 100644 --- a/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/html_sanitizer_test.rb @@ -35,4 +35,24 @@ class Redmine::WikiFormatting::HtmlSanitizerTest < ActiveSupport::TestCase input = %(<a href="javascript:alert('hello');">foo</a>) assert_equal "<a>foo</a>", @sanitizer.call(input) end + + def test_should_be_strict_with_task_list_items + to_test = { + %(<input type="checkbox" class="">) => "", + %(<input type="checkbox" class="task-list-item-checkbox other">) => "", + %(<input type="checkbox" class="task-list-item-checkbox" id="item1">) => %(<input type="checkbox" class="task-list-item-checkbox">), + %(<input type="text" class="">) => "", + %(<input />) => "", + %(<ul class="other"></ul) => "<ul></ul>", + %(<ul class="contains-task-list"></ul) => "<ul class=\"contains-task-list\"></ul>", + %(<ul class="contains-task-list" id="list1"></ul) => "<ul class=\"contains-task-list\"></ul>", + %(<li class="other"></li>) => "", + %(<li id="other"></li>) => "", + %(<li class="task-list-item"></li>) => "", + %(<li class="task-list-item">Item 1</li>) => "Item 1", + } + to_test.each do |input, result| + assert_equal result, @sanitizer.call(input) + end + end end diff --git a/test/unit/lib/redmine/wiki_formatting/macros_test.rb b/test/unit/lib/redmine/wiki_formatting/macros_test.rb index f23c76fdd..a41428266 100644 --- a/test/unit/lib/redmine/wiki_formatting/macros_test.rb +++ b/test/unit/lib/redmine/wiki_formatting/macros_test.rb @@ -140,12 +140,12 @@ class Redmine::WikiFormatting::MacrosTest < Redmine::HelperTest def test_macro_exception_should_be_displayed Redmine::WikiFormatting::Macros.macro :exception do |obj, args| - raise "My message" + raise "My exception's message" end text = "{{exception}}" assert_include( - '<div class="flash error">Error executing the <strong>exception</strong> macro (My message)</div>', + '<div class="flash error">Error executing the <strong>exception</strong> macro (My exception's message)</div>', textilizable(text) ) 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 a7b1843dc..df9088027 100644 --- a/test/unit/member_test.rb +++ b/test/unit/member_test.rb @@ -58,6 +58,39 @@ class MemberTest < ActiveSupport::TestCase assert_equal 2, @jsmith.reload.roles.size end + def test_update_roles_with_inherited_roles + User.current = User.find(1) + + project = Project.find(1) + group_a = Group.generate! + group_b = Group.generate! + test_user = User.generate! + group_a.users << test_user + group_b.users << test_user + + # Verify that inherited roles are correctly assigned + group_a_member = Member.new(project: project, user_id: group_a.id) + group_a_member.set_editable_role_ids([1]) # Add Manager role to Group A + group_b_member = Member.new(project: project, user_id: group_b.id) + group_b_member.set_editable_role_ids([1, 2]) # Add Manager and Developer roles to Group B + project.members << [group_a_member, group_b_member] + test_user_member = test_user.members.find_by(project_id: project.id) + assert_equal [ # [role_id, inherited_from] + [1, group_a_member.member_roles.find_by(role_id: 1).id], + [1, group_b_member.member_roles.find_by(role_id: 1).id], + [2, group_b_member.member_roles.find_by(role_id: 2).id], + ].sort, test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]}.sort + + # Verify that a new non-inherited role is added and inherited roles are maintained + test_user_member.set_editable_role_ids([3]) # Add Reporter role to test_user + assert_equal [ # [role_id, inherited_from] + [1, group_a_member.member_roles.find_by(role_id: 1).id], + [1, group_b_member.member_roles.find_by(role_id: 1).id], + [2, group_b_member.member_roles.find_by(role_id: 2).id], + [3, nil] + ].sort, test_user_member.member_roles.map{|r| [r.role_id, r.inherited_from]}.sort + end + def test_validate member = Member.new(:project_id => 1, :user_id => 2, :role_ids => [2]) # same use cannot have more than one membership for a project @@ -75,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/project_admin_query_test.rb b/test/unit/project_admin_query_test.rb new file mode 100644 index 000000000..8e58e2efb --- /dev/null +++ b/test/unit/project_admin_query_test.rb @@ -0,0 +1,138 @@ +# 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 ProjectAdminQueryTest < ActiveSupport::TestCase + include Redmine::I18n + + def test_filter_values_be_arrays + q = ProjectAdminQuery.new + assert_nil q.project + + q.available_filters.each do |name, filter| + values = filter.values + assert (values.nil? || values.is_a?(Array)), + "#values for #{name} filter returned a #{values.class.name}" + end + end + + def test_project_statuses_filter_should_return_project_statuses + set_language_if_valid 'en' + query = ProjectAdminQuery.new(:name => '_') + query.filters = {'status' => {:operator => '=', :values => []}} + values = query.available_filters['status'][:values] + assert_equal ['active', 'closed', 'archived', 'scheduled for deletion'], values.map(&:first) + assert_equal ['1', '5', '9', '10'], values.map(&:second) + end + + def test_default_columns + q = ProjectAdminQuery.new + assert q.columns.any? + assert q.inline_columns.any? + assert q.block_columns.empty? + end + + def test_available_columns_should_include_project_custom_fields + query = ProjectAdminQuery.new + assert_include :cf_3, query.available_columns.map(&:name) + end + + def test_available_display_types_should_always_returns_list + query = ProjectAdminQuery.new + assert_equal ['list'], query.available_display_types + end + + def test_display_type_should_returns_list + ProjectAdminQuery.new.available_display_types.each do |t| + with_settings :project_list_display_type => t do + q = ProjectAdminQuery.new + assert_equal 'list', q.display_type + end + end + end + + def test_no_default_project_admin_query + user = User.find(1) + query = ProjectQuery.find(11) + user_query = ProjectQuery.find(12) + user_query.update(visibility: Query::VISIBILITY_PUBLIC) + + [nil, user, User.anonymous].each do |u| + assert_nil ProjectAdminQuery.default(user: u) + end + + # ignore the default_project_query for admin queries + with_settings :default_project_query => query.id do + [nil, user, User.anonymous].each do |u| + assert_nil ProjectAdminQuery.default(user: u) + end + end + + # user default, overrides global default + user.pref.default_project_query = user_query.id + user.pref.save + + with_settings :default_project_query => query.id do + assert_nil ProjectAdminQuery.default(user: user) + end + end + + def test_project_statuses_values_should_return_all_statuses + set_language_if_valid 'en' + q = ProjectAdminQuery.new + assert_equal [ + ["active", "1"], + ["closed", "5"], + ["archived", "9"], + ["scheduled for deletion", "10"] + ], q.project_statuses_values + end + + def test_base_scope_should_return_all_projects + q = ProjectAdminQuery.new + assert_equal Project.all, q.base_scope + end + + def test_results_scope_has_last_activity_date + q = ProjectAdminQuery.generate!(column_names: [:last_activity_date]) + result_projects = q.results_scope({}) + + assert_kind_of ActiveRecord::Relation, result_projects + assert_equal Project, result_projects.klass + + last_activitiy_date = result_projects.find{|p| p.id == 1}.instance_variable_get(:@last_activity_date) + assert_not_nil last_activitiy_date + assert_equal Redmine::Activity::Fetcher.new(User.current).events(nil, nil, :project => Project.find(1)).first.updated_on, last_activitiy_date + end + + def test_results_scope_with_offset_and_limit + q = ProjectAdminQuery.new + + ((q.results_scope.count / 2) + 1).times do |i| + limit = 2 + offset = i * 2 + + scope_without = q.results_scope.offset(offset).limit(limit).ids + scope_with = q.results_scope(:offset => offset, :limit => limit).ids + + assert_equal scope_without, scope_with + end + end +end diff --git a/test/unit/project_query_test.rb b/test/unit/project_query_test.rb index 3e232f820..2b8c0ea8d 100644 --- a/test/unit/project_query_test.rb +++ b/test/unit/project_query_test.rb @@ -56,16 +56,9 @@ class ProjectQueryTest < ActiveSupport::TestCase def test_available_display_types_should_returns_bord_and_list query = ProjectQuery.new - query.admin_projects = nil assert_equal ['board', 'list'], query.available_display_types end - def test_available_display_types_should_always_returns_list_when_admin_projects_is_set - query = ProjectQuery.new - query.admin_projects = 1 - assert_equal ['list'], query.available_display_types - end - def test_display_type_default_should_equal_with_setting_project_list_display_type ProjectQuery.new.available_display_types.each do |t| with_settings :project_list_display_type => t do @@ -81,8 +74,10 @@ class ProjectQueryTest < ActiveSupport::TestCase user_query = ProjectQuery.find(12) user_query.update(visibility: Query::VISIBILITY_PUBLIC) - [nil, user, User.anonymous].each do |u| - assert_nil IssueQuery.default(user: u) + with_settings :default_project_query => nil do + [nil, user, User.anonymous].each do |u| + assert_nil ProjectQuery.default(user: u) + end end # only global default is set @@ -110,38 +105,17 @@ class ProjectQueryTest < ActiveSupport::TestCase assert_nil ProjectQuery.default end - def test_display_type_should_returns_list_when_admin_projects_is_set - q = ProjectQuery.new - q.admin_projects = 1 - assert_equal 'list', q.display_type - end - def test_project_statuses_values_should_equal_ancestors_return ancestor = Query.new q = ProjectQuery.new assert_equal ancestor.project_statuses_values, q.project_statuses_values end - def test_project_statuses_values_should_includes_project_status_archeved_when_admin_projects_is_set - q = ProjectQuery.new - q.admin_projects = 1 - assert_includes q.project_statuses_values, [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s] - Query.new.project_statuses_values.each do |status| - assert_includes q.project_statuses_values, status - end - end - def test_base_scope_should_return_visible_projects q = ProjectQuery.new assert_equal Project.visible, q.base_scope end - def test_base_scope_should_return_all_projects_when_admin_projects_is_set - q = ProjectQuery.new - q.admin_projects = 1 - assert_equal Project.all, q.base_scope - end - def test_results_scope_has_last_activity_date q = ProjectQuery.generate!(column_names: [:last_activity_date]) result_projects = q.results_scope({}) diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index ff2cef903..155a74b64 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -623,7 +623,7 @@ class QueryTest < ActiveSupport::TestCase query.add_filter('due_date', '><t+', ['15']) issues = find_issues_with_query(query) assert !issues.empty? - issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))} + issues.each {|issue| assert(issue.due_date.between?(Date.today, (Date.today + 15)))} end def test_operator_less_than_ago @@ -641,7 +641,7 @@ class QueryTest < ActiveSupport::TestCase query.add_filter('due_date', '><t-', ['3']) issues = find_issues_with_query(query) assert !issues.empty? - issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)} + issues.each {|issue| assert(issue.due_date.between?((Date.today - 3), Date.today))} end def test_operator_more_than_ago @@ -2326,7 +2326,7 @@ class QueryTest < ActiveSupport::TestCase values = issues.filter_map do |i| begin - Kernel.Float(i.custom_value_for(c.custom_field).to_s) + Kernel.Float(i.custom_value_for(c.custom_field).to_s, exception: false) rescue nil end 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/repository_bazaar_test.rb b/test/unit/repository_bazaar_test.rb index 23f3ce48f..5fec37973 100644 --- a/test/unit/repository_bazaar_test.rb +++ b/test/unit/repository_bazaar_test.rb @@ -50,6 +50,7 @@ class RepositoryBazaarTest < ActiveSupport::TestCase :log_encoding => 'UTF-8' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_blank_path_to_repository_error_message diff --git a/test/unit/repository_cvs_test.rb b/test/unit/repository_cvs_test.rb index af995eac0..84d0ed80b 100644 --- a/test/unit/repository_cvs_test.rb +++ b/test/unit/repository_cvs_test.rb @@ -36,6 +36,7 @@ class RepositoryCvsTest < ActiveSupport::TestCase :url => MODULE_NAME, :log_encoding => 'UTF-8') assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_blank_module_error_message diff --git a/test/unit/repository_git_test.rb b/test/unit/repository_git_test.rb index ec1ca5157..857be9442 100644 --- a/test/unit/repository_git_test.rb +++ b/test/unit/repository_git_test.rb @@ -41,6 +41,7 @@ class RepositoryGitTest < ActiveSupport::TestCase :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_nondefault_repo_with_blank_identifier_destruction diff --git a/test/unit/repository_mercurial_test.rb b/test/unit/repository_mercurial_test.rb index 861729bac..991d19a6d 100644 --- a/test/unit/repository_mercurial_test.rb +++ b/test/unit/repository_mercurial_test.rb @@ -35,6 +35,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase :path_encoding => 'ISO-8859-1' ) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_blank_path_to_repository_error_message @@ -168,7 +169,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase @repository.fetch_changesets @project.reload assert_equal NUM_REV, @repository.changesets.count - assert_equal 53, @repository.filechanges.count + assert_equal 47, @repository.filechanges.count rev0 = @repository.changesets.find_by_revision('0') assert_equal "Initial import.\nThe repository contains 3 files.", rev0.comments @@ -261,13 +262,13 @@ class RepositoryMercurialTest < ActiveSupport::TestCase @repository.latest_changesets( '/sql_escape/percent%dir/percent%file1.txt', nil ) - assert_equal %w|30 11 10 9|, changesets.collect(&:revision) + assert_equal %w|11 10 9|, changesets.collect(&:revision) changesets = @repository.latest_changesets( '/sql_escape/underscore_dir/understrike_file.txt', nil ) - assert_equal %w|30 12 9|, changesets.collect(&:revision) + assert_equal %w|12 9|, changesets.collect(&:revision) changesets = @repository.latest_changesets('README', nil) assert_equal %w|31 30 28 17 8 6 1 0|, changesets.collect(&:revision) @@ -284,7 +285,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase path = 'sql_escape/percent%dir' changesets = @repository.latest_changesets(path, nil) - assert_equal %w|30 13 11 10 9|, changesets.collect(&:revision) + assert_equal %w|13 11 10 9|, changesets.collect(&:revision) changesets = @repository.latest_changesets(path, '11') assert_equal %w|11 10 9|, changesets.collect(&:revision) @@ -294,7 +295,7 @@ class RepositoryMercurialTest < ActiveSupport::TestCase path = 'sql_escape/underscore_dir' changesets = @repository.latest_changesets(path, nil) - assert_equal %w|30 13 12 9|, changesets.collect(&:revision) + assert_equal %w|13 12 9|, changesets.collect(&:revision) changesets = @repository.latest_changesets(path, '12') assert_equal %w|12 9|, changesets.collect(&:revision) diff --git a/test/unit/repository_subversion_test.rb b/test/unit/repository_subversion_test.rb index b4590ce31..dfdf520e7 100644 --- a/test/unit/repository_subversion_test.rb +++ b/test/unit/repository_subversion_test.rb @@ -30,6 +30,7 @@ class RepositorySubversionTest < ActiveSupport::TestCase @repository = Repository::Subversion.create(:project => @project, :url => self.class.subversion_repository_url) assert @repository + skip "SCM command is unavailable" unless @repository.class.scm_available end def test_invalid_url diff --git a/test/unit/repository_test.rb b/test/unit/repository_test.rb index 53b5e0ee7..84c22a73f 100644 --- a/test/unit/repository_test.rb +++ b/test/unit/repository_test.rb @@ -455,7 +455,7 @@ class RepositoryTest < ActiveSupport::TestCase def test_stats_by_author_reflect_changesets_and_changes repository = Repository.find(10) - expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}} + expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}} assert_equal expected, repository.stats_by_author set = Changeset.create!( @@ -467,7 +467,7 @@ class RepositoryTest < ActiveSupport::TestCase ) Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1') Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2') - expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}} + expected = {"Dave Lopper"=>{:commits_count=>12, :changes_count=>5}} assert_equal expected, repository.stats_by_author end @@ -476,7 +476,7 @@ class RepositoryTest < ActiveSupport::TestCase # to ensure things are dynamically linked to Users User.find_by_login("dlopper").update_attribute(:firstname, "Dave's") repository = Repository.find(10) - expected = {"Dave's Lopper"=>{:commits_count=>10, :changes_count=>3}} + expected = {"Dave's Lopper"=>{:commits_count=>11, :changes_count=>3}} assert_equal expected, repository.stats_by_author end @@ -502,7 +502,7 @@ class RepositoryTest < ActiveSupport::TestCase # with committer="dlopper <dlopper@somefoo.net>" repository = Repository.find(10) - expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}} + expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}} assert_equal expected, repository.stats_by_author set = Changeset.create!( @@ -513,7 +513,7 @@ class RepositoryTest < ActiveSupport::TestCase :comments => 'Another commit by foo.' ) - expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}} + expected = {"Dave Lopper"=>{:commits_count=>12, :changes_count=>3}} assert_equal expected, repository.stats_by_author 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/time_entry_test.rb b/test/unit/time_entry_test.rb index 6d04619e8..19b1ba2a4 100644 --- a/test/unit/time_entry_test.rb +++ b/test/unit/time_entry_test.rb @@ -175,6 +175,18 @@ class TimeEntryTest < ActiveSupport::TestCase end end + def test_should_not_accept_closed_issue + with_settings :timelog_accept_closed_issues => '0' do + project = Project.find(1) + entry = TimeEntry.generate project: project + issue = project.issues.to_a.detect(&:closed?) + entry.issue = issue + assert !entry.save + assert entry.errors[:base].present? + assert_equal 'Cannot log time on a closed issue', entry.errors[:base].first + end + end + def test_should_require_spent_on with_settings :timelog_accept_future_dates => '0' do entry = TimeEntry.find(1) diff --git a/test/unit/user_query_test.rb b/test/unit/user_query_test.rb index 1f8ce3464..ef31ba2c2 100644 --- a/test/unit/user_query_test.rb +++ b/test/unit/user_query_test.rb @@ -209,6 +209,30 @@ class UserQueryTest < ActiveSupport::TestCase assert_equal [2, 1], users.pluck(:id) end + def test_user_query_is_only_visible_to_admins + q = UserQuery.new(name: '_') + assert q.save + + admin = User.admin(true).first + user = User.admin(false).first + + assert q.visible?(admin) + assert_include q, UserQuery.visible(admin).to_a + + assert_not q.visible?(user) + assert_not_include q, UserQuery.visible(user) + end + + def test_user_query_is_only_editable_by_admins + q = UserQuery.new(name: '_') + + admin = User.admin(true).first + user = User.admin(false).first + + assert q.editable_by?(admin) + assert_not q.editable_by?(user) + end + def find_users_with_query(query) User.where(query.statement).to_a 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 |