repository_test.rb 17.6 KB
Newer Older
1
# frozen_string_literal: true
2

3
# Redmine - project management software
jplang's avatar
jplang committed
4
# Copyright (C) 2006-2017  Jean-Philippe Lang
5 6 7 8 9
#
# 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.
10
#
11 12 13 14
# 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.
15
#
16 17 18 19
# 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.

20
require File.expand_path('../../test_helper', __FILE__)
21

22
class RepositoryTest < ActiveSupport::TestCase
23 24 25
  fixtures :projects,
           :trackers,
           :projects_trackers,
26
           :enabled_modules,
27 28 29
           :repositories,
           :issues,
           :issue_statuses,
30
           :issue_categories,
31 32 33
           :changesets,
           :changes,
           :users,
marutosijp's avatar
marutosijp committed
34
           :email_addresses,
35 36 37
           :members,
           :member_roles,
           :roles,
38 39 40
           :enumerations,
           :user_preferences,
           :watchers
41

42 43
  include Redmine::I18n

44
  def setup
45
    User.current = nil
46 47
    @repository = Project.find(1).repository
  end
48

49 50 51 52 53 54 55 56
  def test_blank_log_encoding_error_message
    set_language_if_valid 'en'
    repo = Repository::Bazaar.new(
                        :project      => Project.find(3),
                        :url          => "/test",
                        :log_encoding => ''
                      )
    assert !repo.save
57
    assert_include "Commit messages encoding cannot be blank",
58 59 60 61 62 63 64 65 66 67
                   repo.errors.full_messages
  end

  def test_blank_log_encoding_error_message_fr
    set_language_if_valid 'fr'
    repo = Repository::Bazaar.new(
                        :project      => Project.find(3),
                        :url          => "/test"
                      )
    assert !repo.save
68
    assert_include 'Encodage des messages de commit doit être renseigné(e)', repo.errors.full_messages
69 70
  end

71
  def test_create
72
    repository = Repository::Subversion.new(:project => Project.find(3))
73
    assert !repository.save
74

75
    repository.url = "svn://localhost"
76 77
    assert repository.save
    repository.reload
78

79
    project = Project.find(3)
80
    assert_equal repository, project.repository
81
  end
82

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  def test_2_repositories_with_same_identifier_in_different_projects_should_be_valid
    Repository::Subversion.create!(:project_id => 2, :identifier => 'foo', :url => 'file:///foo')
    r = Repository::Subversion.new(:project_id => 3, :identifier => 'foo', :url => 'file:///bar')
    assert r.save
  end

  def test_2_repositories_with_same_identifier_should_not_be_valid
    Repository::Subversion.create!(:project_id => 3, :identifier => 'foo', :url => 'file:///foo')
    r = Repository::Subversion.new(:project_id => 3, :identifier => 'foo', :url => 'file:///bar')
    assert !r.save
  end

  def test_2_repositories_with_blank_identifier_should_not_be_valid
    Repository::Subversion.create!(:project_id => 3, :identifier => '', :url => 'file:///foo')
    r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
    assert !r.save
  end

101 102 103 104 105 106 107 108 109 110 111 112
  def test_2_repositories_with_blank_identifier_and_one_as_default_should_not_be_valid
    Repository::Subversion.create!(:project_id => 3, :identifier => '', :url => 'file:///foo', :is_default => true)
    r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
    assert !r.save
  end

  def test_2_repositories_with_blank_and_nil_identifier_should_not_be_valid
    Repository::Subversion.create!(:project_id => 3, :identifier => nil, :url => 'file:///foo')
    r = Repository::Subversion.new(:project_id => 3, :identifier => '', :url => 'file:///bar')
    assert !r.save
  end

113
  def test_first_repository_should_be_set_as_default
114 115 116 117 118
    repository1 = Repository::Subversion.new(
                      :project => Project.find(3),
                      :identifier => 'svn1',
                      :url => 'file:///svn1'
                    )
119 120 121
    assert repository1.save
    assert repository1.is_default?

122 123 124 125 126
    repository2 = Repository::Subversion.new(
                      :project => Project.find(3),
                      :identifier => 'svn2',
                      :url => 'file:///svn2'
                    )
127 128 129 130 131 132 133
    assert repository2.save
    assert !repository2.is_default?

    assert_equal repository1, Project.find(3).repository
    assert_equal [repository1, repository2], Project.find(3).repositories.sort
  end

134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
  def test_default_repository_should_be_one
    assert_equal 0, Project.find(3).repositories.count
    repository1 = Repository::Subversion.new(
                      :project => Project.find(3),
                      :identifier => 'svn1',
                      :url => 'file:///svn1'
                    )
    assert repository1.save
    assert repository1.is_default?

    repository2 = Repository::Subversion.new(
                      :project => Project.find(3),
                      :identifier => 'svn2',
                      :url => 'file:///svn2',
                      :is_default => true
                    )
    assert repository2.save
    assert repository2.is_default?
    repository1.reload
    assert !repository1.is_default?

    assert_equal repository2, Project.find(3).repository
    assert_equal [repository2, repository1], Project.find(3).repositories.sort
  end

159 160 161 162 163 164 165 166
  def test_identifier_should_accept_letters_digits_dashes_and_underscores
    r = Repository::Subversion.new(
      :project_id => 3,
      :identifier => 'svn-123_45',
      :url => 'file:///svn'
    )
    assert r.save
  end
167

168 169 170 171 172
  def test_identifier_should_not_be_frozen_for_a_new_repository
    assert_equal false, Repository.new.identifier_frozen?
  end

  def test_identifier_should_not_be_frozen_for_a_saved_repository_with_blank_identifier
173
    Repository.where(:id => 10).update_all(["identifier = ''"])
174 175 176 177
    assert_equal false, Repository.find(10).identifier_frozen?
  end

  def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
178
    Repository.where(:id => 10).update_all(["identifier = 'abc123'"])
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    assert_equal true, Repository.find(10).identifier_frozen?
  end

  def test_identifier_should_not_accept_change_if_frozen
    r = Repository.new(:identifier => 'foo')
    r.stubs(:identifier_frozen?).returns(true)

    r.identifier = 'bar'
    assert_equal 'foo', r.identifier
  end

  def test_identifier_should_accept_change_if_not_frozen
    r = Repository.new(:identifier => 'foo')
    r.stubs(:identifier_frozen?).returns(false)

    r.identifier = 'bar'
    assert_equal 'bar', r.identifier
  end
197

198
  def test_destroy
199 200 201 202
    repository = Repository.find(10)
    changesets = repository.changesets.count
    changes = repository.filechanges.count

203 204 205 206 207 208
    assert_difference 'Changeset.count', -changesets do
      assert_difference 'Change.count', -changes do
        Repository.find(10).destroy
      end
    end
  end
209

210 211
  def test_destroy_should_delete_parents_associations
    changeset = Changeset.find(102)
212
    changeset.parents = Changeset.where(:id => [100, 101]).to_a
213
    assert_difference 'Changeset.connection.select_all("select * from changeset_parents").count', -2 do
214 215 216 217
      Repository.find(10).destroy
    end
  end

218 219
  def test_destroy_should_delete_issues_associations
    changeset = Changeset.find(102)
220
    changeset.issues = Issue.where(:id => [1, 2]).to_a
221
    assert_difference 'Changeset.connection.select_all("select * from changesets_issues").count', -2 do
222 223 224 225
      Repository.find(10).destroy
    end
  end

226 227
  def test_should_not_create_with_disabled_scm
    # disable Subversion
jplang's avatar
jplang committed
228
    with_settings :enabled_scm => ['Mercurial', 'Git'] do
229 230
      repository = Repository::Subversion.new(
                      :project => Project.find(3), :url => "svn://localhost")
231
      assert !repository.save
232 233
      assert_include I18n.translate('activerecord.errors.messages.invalid'),
                     repository.errors[:type]
234
    end
235
  end
236

237
  def test_scan_changesets_for_issue_ids
jplang's avatar
jplang committed
238
    Setting.default_language = 'en'
jplang's avatar
jplang committed
239
    Setting.commit_ref_keywords = 'refs , references, IssueID'
240
    Setting.commit_update_keywords = [
241 242 243
      {'keywords' => 'fixes , closes',
       'status_id' => IssueStatus.where(:is_closed => true).first.id,
       'done_ratio' => '90'}
244
    ]
245 246
    Setting.default_language = 'en'
    ActionMailer::Base.deliveries.clear
247

248
    # make sure issue 1 is not already closed
249
    fixed_issue = Issue.find(1)
jplang's avatar
jplang committed
250
    assert !fixed_issue.closed?
251
    old_status = fixed_issue.status
252

253 254 255
    with_settings :notified_events => %w(issue_added issue_updated) do
      Repository.scan_changesets_for_issue_ids
    end
256
    assert_equal [101, 102], Issue.find(3).changeset_ids
257

258
    # fixed issues
259
    fixed_issue.reload
jplang's avatar
jplang committed
260
    assert fixed_issue.closed?
261 262
    assert_equal 90, fixed_issue.done_ratio
    assert_equal [101], fixed_issue.changeset_ids
263

264
    # issue change
jplang's avatar
jplang committed
265
    journal = fixed_issue.journals.reorder('created_on desc').first
266 267
    assert_equal User.find_by_login('dlopper'), journal.user
    assert_equal 'Applied in changeset r2.', journal.notes
268

269 270 271 272 273 274 275 276 277
    # 5 email notifications, 2 for #1, 3 for #2
    assert_equal 5, ActionMailer::Base.deliveries.size
    ActionMailer::Base.deliveries.first(2).each do |mail|
      assert_not_nil mail
      assert mail.subject.starts_with?(
          "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
      assert_mail_body_match(
          "Status changed from #{old_status} to #{fixed_issue.status}", mail)
    end
278

279 280 281
    # ignoring commits referencing an issue of another project
    assert_equal [], Issue.find(4).changesets
  end
282

283
  def test_for_changeset_comments_strip
284
    repository = Repository::Mercurial.create(
285
                    :project => Project.find(4),
286
                    :url => '/foo/bar/baz' )
287 288
    long_whitespace = "                                                "
    expected_comment = "This is a loooooooooooooooooooooooooooong comment"
289
    comment = +"#{expected_comment}#{long_whitespace}\n"
290
    3.times {comment << "#{long_whitespace}\n"}
291
    changeset = Changeset.new(
292 293 294
      :comments => comment, :commit_date => Time.now,
      :revision => 0, :scmid => 'f39b7922fb3c',
      :committer => 'foo <foo@example.com>',
295 296
      :committed_on => Time.now, :repository => repository)
    assert(changeset.save)
297 298
    assert_not_equal comment, changeset.comments
    assert_equal     expected_comment, changeset.comments
299 300
    assert_equal     expected_comment, changeset.short_comments
    assert_equal     "", changeset.long_comments
301
  end
302

303
  def test_for_urls_strip_cvs
304 305 306 307 308
    repository = Repository::Cvs.create(
        :project => Project.find(4),
        :url => ' :pserver:login:password@host:/path/to/the/repository',
        :root_url => 'foo  ',
        :log_encoding => 'UTF-8')
309 310
    assert repository.save
    repository.reload
311 312
    assert_equal ':pserver:login:password@host:/path/to/the/repository',
                  repository.url
313 314
    assert_equal 'foo', repository.root_url
  end
315

316 317 318 319 320 321 322 323 324
  def test_for_urls_strip_subversion
    repository = Repository::Subversion.create(
        :project => Project.find(4),
        :url => ' file:///dummy   ')
    assert repository.save
    repository.reload
    assert_equal 'file:///dummy', repository.url
  end

325 326 327 328 329 330 331 332 333
  def test_for_urls_strip_git
    repository = Repository::Git.create(
        :project => Project.find(4),
        :url => ' c:\dummy   ')
    assert repository.save
    repository.reload
    assert_equal 'c:\dummy', repository.url
  end

334
  def test_manual_user_mapping
335
    assert_no_difference "Changeset.where('user_id <> 2').count" do
336 337 338 339 340 341 342
      c = Changeset.create!(
              :repository => @repository,
              :committer => 'foo',
              :committed_on => Time.now,
              :revision => 100,
              :comments => 'Committed by foo.'
            )
343 344 345 346
      assert_nil c.user
      @repository.committer_ids = {'foo' => '2'}
      assert_equal User.find(2), c.reload.user
      # committer is now mapped
347 348 349 350 351 352 353
      c = Changeset.create!(
              :repository => @repository,
              :committer => 'foo',
              :committed_on => Time.now,
              :revision => 101,
              :comments => 'Another commit by foo.'
            )
354 355 356
      assert_equal User.find(2), c.user
    end
  end
357

358
  def test_auto_user_mapping_by_username
359
    c = Changeset.create!(
360 361
          :repository   => @repository,
          :committer    => 'jsmith',
362
          :committed_on => Time.now,
363 364
          :revision     => 100,
          :comments     => 'Committed by john.'
365
        )
366 367
    assert_equal User.find(2), c.user
  end
368

369
  def test_auto_user_mapping_by_email
370
    c = Changeset.create!(
371 372
          :repository   => @repository,
          :committer    => 'john <jsmith@somenet.foo>',
373
          :committed_on => Time.now,
374 375
          :revision     => 100,
          :comments     => 'Committed by john.'
376
        )
377 378
    assert_equal User.find(2), c.user
  end
379 380 381 382 383 384

  def test_filesystem_avaialbe
    klass = Repository::Filesystem
    assert klass.scm_adapter_class
    assert_equal true, klass.scm_available
  end
385

386 387 388 389 390 391
  def test_extra_info_should_not_return_non_hash_value
    repo = Repository.new
    repo.extra_info = "foo"
    assert_nil repo.extra_info
  end

392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
  def test_merge_extra_info
    repo = Repository::Subversion.new(:project => Project.find(3))
    assert !repo.save
    repo.url = "svn://localhost"
    assert repo.save
    repo.reload
    project = Project.find(3)
    assert_equal repo, project.repository
    assert_nil repo.extra_info
    h1 = {"test_1" => {"test_11" => "test_value_11"}}
    repo.merge_extra_info(h1)
    assert_equal h1, repo.extra_info
    h2 = {"test_2" => {
                   "test_21" => "test_value_21",
                   "test_22" => "test_value_22",
                  }}
    repo.merge_extra_info(h2)
    assert_equal (h = {"test_11" => "test_value_11"}),
                 repo.extra_info["test_1"]
    assert_equal "test_value_21",
                 repo.extra_info["test_2"]["test_21"]
    h3 = {"test_2" => {
                   "test_23" => "test_value_23",
                   "test_24" => "test_value_24",
                  }}
    repo.merge_extra_info(h3)
    assert_equal (h = {"test_11" => "test_value_11"}),
                 repo.extra_info["test_1"]
    assert_nil repo.extra_info["test_2"]["test_21"]
    assert_equal "test_value_23",
                 repo.extra_info["test_2"]["test_23"]
  end
424 425 426 427 428 429 430 431 432

  def test_sort_should_not_raise_an_error_with_nil_identifiers
    r1 = Repository.new
    r2 = Repository.new

    assert_nothing_raised do
      [r1, r2].sort
    end
  end
433 434 435 436

  def test_stats_by_author_reflect_changesets_and_changes
    repository = Repository.find(10)

437
    expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
438 439 440 441 442 443 444 445 446
    assert_equal expected, repository.stats_by_author

    set = Changeset.create!(
      :repository => repository,
      :committer => 'dlopper',
      :committed_on => Time.now,
      :revision => 101,
      :comments => 'Another commit by foo.'
    )
447 448
    Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1')
    Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2')
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
    expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>5}}
    assert_equal expected, repository.stats_by_author
  end

  def test_stats_by_author_honnor_committers
    # in fact it is really tested above, but let's have a dedicated test
    # 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}}
    assert_equal expected, repository.stats_by_author
  end

  def test_stats_by_author_doesnt_drop_unmapped_users
    repository = Repository.find(10)
    Changeset.create!(
      :repository => repository,
      :committer => 'unnamed <foo@bar.net>',
      :committed_on => Time.now,
      :revision => 101,
      :comments => 'Another commit by foo.'
    )

    assert repository.stats_by_author.has_key?("unnamed <foo@bar.net>")
  end

  def test_stats_by_author_merge_correctly
    # as we honnor users->committer map and it's not injective,
    # we must be sure merges happen correctly and stats are not
    # wiped out when two source counts map to the same user.
    #
    # Here we have Changeset's with committer="dlopper" and others
    # with committer="dlopper <dlopper@somefoo.net>"
    repository = Repository.find(10)

    expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
    assert_equal expected, repository.stats_by_author

    set = Changeset.create!(
      :repository => repository,
      :committer => 'dlopper <dlopper@somefoo.net>',
      :committed_on => Time.now,
      :revision => 101,
      :comments => 'Another commit by foo.'
    )

    expected = {"Dave Lopper"=>{:commits_count=>11, :changes_count=>3}}
496 497
    assert_equal expected, repository.stats_by_author
  end
jplang's avatar
jplang committed
498 499 500 501 502 503

  def test_fetch_changesets
    # 2 repositories in fixtures
    Repository::Subversion.any_instance.expects(:fetch_changesets).twice.returns(true)
    Repository.fetch_changesets
  end
504 505 506 507 508 509 510 511 512 513 514 515 516 517

  def test_repository_class
    assert_equal Repository::Subversion, Repository.repository_class('Subversion')
    assert_equal Repository::Git, Repository.repository_class('Git')
    assert_nil Repository.factory('Serializer')
    assert_nil Repository.factory('Query')
  end

  def test_factory
    assert_instance_of Repository::Subversion, Repository.factory('Subversion')
    assert_instance_of Repository::Git, Repository.factory('Git')
    assert_nil Repository.factory('Serializer')
    assert_nil Repository.factory('Query')
  end
518
end