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

18
require File.expand_path('../../test_helper', __FILE__)
19

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

38 39
  include Redmine::I18n

40 41 42
  def setup
    @repository = Project.find(1).repository
  end
43

44 45 46 47 48 49 50 51
  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
52
    assert_include "Commit messages encoding cannot be blank",
53 54 55 56 57
                   repo.errors.full_messages
  end

  def test_blank_log_encoding_error_message_fr
    set_language_if_valid 'fr'
jplang's avatar
jplang committed
58
    str = "Encodage des messages de commit doit \xc3\xaatre renseign\xc3\xa9(e)".force_encoding('UTF-8')
59 60 61 62 63 64 65 66
    repo = Repository::Bazaar.new(
                        :project      => Project.find(3),
                        :url          => "/test"
                      )
    assert !repo.save
    assert_include str, repo.errors.full_messages
  end

jplang's avatar
jplang committed
67
  def test_create
68
    repository = Repository::Subversion.new(:project => Project.find(3))
69
    assert !repository.save
70

jplang's avatar
jplang committed
71
    repository.url = "svn://localhost"
72 73
    assert repository.save
    repository.reload
74

75
    project = Project.find(3)
jplang's avatar
jplang committed
76
    assert_equal repository, project.repository
77
  end
78

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
  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

97 98 99 100 101 102 103 104 105 106 107 108
  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

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

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

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

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
  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

155 156 157 158 159 160 161 162
  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
163

164 165 166 167 168
  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
169
    Repository.where(:id => 10).update_all(["identifier = ''"])
170 171 172 173
    assert_equal false, Repository.find(10).identifier_frozen?
  end

  def test_identifier_should_be_frozen_for_a_saved_repository_with_valid_identifier
174
    Repository.where(:id => 10).update_all(["identifier = 'abc123'"])
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
    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
193

194
  def test_destroy
195 196 197 198
    repository = Repository.find(10)
    changesets = repository.changesets.count
    changes = repository.filechanges.count

199 200 201 202 203 204
    assert_difference 'Changeset.count', -changesets do
      assert_difference 'Change.count', -changes do
        Repository.find(10).destroy
      end
    end
  end
205

206 207
  def test_destroy_should_delete_parents_associations
    changeset = Changeset.find(102)
jplang's avatar
jplang committed
208
    changeset.parents = Changeset.where(:id => [100, 101]).to_a
209
    assert_difference 'Changeset.connection.select_all("select * from changeset_parents").count', -2 do
210 211 212 213
      Repository.find(10).destroy
    end
  end

214 215
  def test_destroy_should_delete_issues_associations
    changeset = Changeset.find(102)
jplang's avatar
jplang committed
216
    changeset.issues = Issue.where(:id => [1, 2]).to_a
217
    assert_difference 'Changeset.connection.select_all("select * from changesets_issues").count', -2 do
218 219 220 221
      Repository.find(10).destroy
    end
  end

222 223
  def test_should_not_create_with_disabled_scm
    # disable Subversion
224
    with_settings :enabled_scm => ['Darcs', 'Git'] do
225 226
      repository = Repository::Subversion.new(
                      :project => Project.find(3), :url => "svn://localhost")
227
      assert !repository.save
228 229
      assert_include I18n.translate('activerecord.errors.messages.invalid'),
                     repository.errors[:type]
230
    end
231
  end
232

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

244
    # make sure issue 1 is not already closed
245
    fixed_issue = Issue.find(1)
jplang's avatar
jplang committed
246
    assert !fixed_issue.closed?
247
    old_status = fixed_issue.status
248

jplang's avatar
jplang committed
249 250 251
    with_settings :notified_events => %w(issue_added issue_updated) do
      Repository.scan_changesets_for_issue_ids
    end
252
    assert_equal [101, 102], Issue.find(3).changeset_ids
253

254
    # fixed issues
255
    fixed_issue.reload
jplang's avatar
jplang committed
256
    assert fixed_issue.closed?
257 258
    assert_equal 90, fixed_issue.done_ratio
    assert_equal [101], fixed_issue.changeset_ids
259

260
    # issue change
jplang's avatar
jplang committed
261
    journal = fixed_issue.journals.reorder('created_on desc').first
262 263
    assert_equal User.find_by_login('dlopper'), journal.user
    assert_equal 'Applied in changeset r2.', journal.notes
264

265 266 267
    # 2 email notifications
    assert_equal 2, ActionMailer::Base.deliveries.size
    mail = ActionMailer::Base.deliveries.first
jplang's avatar
jplang committed
268
    assert_not_nil mail
269 270
    assert mail.subject.starts_with?(
        "[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
271 272
    assert_mail_body_match(
        "Status changed from #{old_status} to #{fixed_issue.status}", mail)
273

274 275 276
    # ignoring commits referencing an issue of another project
    assert_equal [], Issue.find(4).changesets
  end
277

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

296
  def test_for_urls_strip_cvs
297 298 299 300 301
    repository = Repository::Cvs.create(
        :project => Project.find(4),
        :url => ' :pserver:login:password@host:/path/to/the/repository',
        :root_url => 'foo  ',
        :log_encoding => 'UTF-8')
302 303
    assert repository.save
    repository.reload
304 305
    assert_equal ':pserver:login:password@host:/path/to/the/repository',
                  repository.url
306 307
    assert_equal 'foo', repository.root_url
  end
308

309 310 311 312 313 314 315 316 317
  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

318 319 320 321 322 323 324 325 326
  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

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

351
  def test_auto_user_mapping_by_username
352
    c = Changeset.create!(
353 354
          :repository   => @repository,
          :committer    => 'jsmith',
355
          :committed_on => Time.now,
356 357
          :revision     => 100,
          :comments     => 'Committed by john.'
358
        )
359 360
    assert_equal User.find(2), c.user
  end
361

362
  def test_auto_user_mapping_by_email
363
    c = Changeset.create!(
364 365
          :repository   => @repository,
          :committer    => 'john <jsmith@somenet.foo>',
366
          :committed_on => Time.now,
367 368
          :revision     => 100,
          :comments     => 'Committed by john.'
369
        )
370 371
    assert_equal User.find(2), c.user
  end
372 373 374 375 376 377

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

379 380 381 382 383 384
  def test_extra_info_should_not_return_non_hash_value
    repo = Repository.new
    repo.extra_info = "foo"
    assert_nil repo.extra_info
  end

385 386 387 388 389 390 391 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
  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
417 418 419 420 421 422 423 424 425

  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
426 427 428 429

  def test_stats_by_author_reflect_changesets_and_changes
    repository = Repository.find(10)

430
    expected = {"Dave Lopper"=>{:commits_count=>10, :changes_count=>3}}
431 432 433 434 435 436 437 438 439
    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.'
    )
440 441
    Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file1')
    Change.create!(:changeset => set, :action => 'A', :path => '/path/to/file2')
442 443 444 445 446 447 448 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
    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}}
489 490
    assert_equal expected, repository.stats_by_author
  end
jplang's avatar
jplang committed
491 492 493 494 495 496

  def test_fetch_changesets
    # 2 repositories in fixtures
    Repository::Subversion.any_instance.expects(:fetch_changesets).twice.returns(true)
    Repository.fetch_changesets
  end
497 498 499 500 501 502 503 504 505 506 507 508 509 510

  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
511
end