This ensures inserts to the journal versions are atomtic, so we can avoid locking the journals table. https://community.openproject.com/wp/30594pull/7525/head
parent
5bfa246527
commit
abfaed8b74
@ -0,0 +1,31 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# 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. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class JournalVersion < ActiveRecord::Base |
||||
end |
@ -0,0 +1,28 @@ |
||||
require_relative './migration_utils/utils' |
||||
|
||||
class AddJournalVersionsTable < ActiveRecord::Migration[5.2] |
||||
include ::Migration::Utils |
||||
|
||||
def up |
||||
create_table :journal_versions do |t| |
||||
t.string :journable_type |
||||
t.integer :journable_id |
||||
t.integer :version, default: 0 |
||||
t.index %i[journable_type journable_id version], |
||||
name: 'unique_journal_version', |
||||
unique: true |
||||
end |
||||
|
||||
ActiveRecord::Base.connection.execute <<-SQL |
||||
INSERT INTO journal_versions (journable_type, journable_id, version) |
||||
(SELECT |
||||
journable_type, journable_id, MAX(version) |
||||
FROM journals |
||||
GROUP BY journable_type, journable_id); |
||||
SQL |
||||
end |
||||
|
||||
def down |
||||
drop_table :journal_versions |
||||
end |
||||
end |
@ -0,0 +1,73 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# 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. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
describe JournalVersion, type: :model do |
||||
let!(:work_package) do |
||||
wp = FactoryBot.build(:work_package) |
||||
wp.journal_notes = 'foobar!' |
||||
|
||||
wp.save! |
||||
wp |
||||
end |
||||
|
||||
subject { ::JournalVersion.find_by!(journable_type: 'WorkPackage', id: work_package.id) } |
||||
|
||||
before do |
||||
work_package |
||||
subject |
||||
end |
||||
|
||||
it 'is created when the work package is created' do |
||||
expect(subject.version).to eq 1 |
||||
end |
||||
|
||||
it 'is incremented when the work package is journaled' do |
||||
work_package.subject = 'Foobar!' |
||||
work_package.journal_notes = 'My comment' |
||||
work_package.save! |
||||
|
||||
work_package.reload |
||||
|
||||
expect(work_package.journals.count).to eq 2 |
||||
expect(work_package.journals.first.version).to eq 1 |
||||
expect(work_package.journals.last.version).to eq 2 |
||||
|
||||
subject.reload |
||||
expect(subject.version).to eq 2 |
||||
end |
||||
|
||||
it 'is removed when the work package is removed' do |
||||
expect(subject).to be_present |
||||
|
||||
work_package.destroy! |
||||
|
||||
expect { subject.reload }.to raise_error ActiveRecord::RecordNotFound |
||||
end |
||||
end |
@ -1,279 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# 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. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
require_relative '../legacy_spec_helper' |
||||
|
||||
describe Changeset, type: :model do |
||||
fixtures :all |
||||
|
||||
context 'with notified events', with_settings: { notified_events: %w(work_package_updated) } do |
||||
it 'should ref keywords any' do |
||||
WorkPackage.all.each(&:recreate_initial_journal!) |
||||
|
||||
Setting.commit_fix_status_id = Status.where(['is_closed = ?', true]).first.id |
||||
Setting.commit_fix_done_ratio = '90' |
||||
Setting.commit_ref_keywords = '*' |
||||
Setting.commit_fix_keywords = 'fixes , closes' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: 'New commit (#2). Fixes #1') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [1, 2], c.work_package_ids.sort |
||||
fixed = WorkPackage.find(1) |
||||
assert fixed.closed? |
||||
assert_equal 90, fixed.done_ratio |
||||
assert_equal 2, ActionMailer::Base.deliveries.size |
||||
end |
||||
|
||||
it 'should ref keywords' do |
||||
Setting.commit_ref_keywords = 'refs' |
||||
Setting.commit_fix_keywords = '' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: 'Ignores #2. Refs #1') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [1], c.work_package_ids.sort |
||||
end |
||||
|
||||
it 'should ref keywords any only' do |
||||
Setting.commit_ref_keywords = '*' |
||||
Setting.commit_fix_keywords = '' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: 'Ignores #2. Refs #1') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [1, 2], c.work_package_ids.sort |
||||
end |
||||
|
||||
it 'should ref keywords any with timelog' do |
||||
Setting.commit_ref_keywords = '*' |
||||
Setting.commit_logtime_enabled = '1' |
||||
|
||||
{ |
||||
'2' => 2.0, |
||||
'2h' => 2.0, |
||||
'2hours' => 2.0, |
||||
'15m' => 0.25, |
||||
'15min' => 0.25, |
||||
'3h15' => 3.25, |
||||
'3h15m' => 3.25, |
||||
'3h15min' => 3.25, |
||||
'3:15' => 3.25, |
||||
'3.25' => 3.25, |
||||
'3.25h' => 3.25, |
||||
'3,25' => 3.25, |
||||
'3,25h' => 3.25 |
||||
}.each do |syntax, expected_hours| |
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: 24.hours.ago, |
||||
comments: "Worked on this work_package #1 @#{syntax}", |
||||
revision: '520', |
||||
user: User.find(2)) |
||||
assert_difference 'TimeEntry.count' do |
||||
c.scan_comment_for_work_package_ids |
||||
end |
||||
assert_equal [1], c.work_package_ids.sort |
||||
|
||||
time = TimeEntry.order(Arel.sql('id DESC')).first |
||||
assert_equal 1, time.work_package_id |
||||
assert_equal 1, time.project_id |
||||
assert_equal 2, time.user_id |
||||
assert_equal expected_hours, |
||||
time.hours, |
||||
"@#{syntax} should be logged as #{expected_hours} hours but was #{time.hours}" |
||||
assert_equal Date.yesterday, time.spent_on |
||||
assert time.activity.is_default? |
||||
assert time.comments.include?('r520'), |
||||
"r520 was expected in time_entry comments: #{time.comments}" |
||||
end |
||||
end |
||||
|
||||
it 'should ref keywords closing with timelog' do |
||||
Setting.commit_fix_status_id = Status.where(['is_closed = ?', true]).first.id |
||||
Setting.commit_ref_keywords = '*' |
||||
Setting.commit_fix_keywords = 'fixes , closes' |
||||
Setting.commit_logtime_enabled = '1' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: 'This is a comment. Fixes #1 @4.5, #2 @1', |
||||
user: User.find(2)) |
||||
assert_difference 'TimeEntry.count', 2 do |
||||
c.scan_comment_for_work_package_ids |
||||
end |
||||
|
||||
assert_equal [1, 2], c.work_package_ids.sort |
||||
assert WorkPackage.find(1).closed? |
||||
assert WorkPackage.find(2).closed? |
||||
|
||||
times = TimeEntry.order(Arel.sql('id desc')).limit(2) |
||||
assert_equal [1, 2], times.map(&:work_package_id).sort |
||||
end |
||||
|
||||
it 'should ref keywords any line start' do |
||||
Setting.commit_ref_keywords = '*' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: '#1 is the reason of this commit') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [1], c.work_package_ids.sort |
||||
end |
||||
|
||||
it 'should ref keywords allow brackets around a work package number' do |
||||
Setting.commit_ref_keywords = '*' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: '[#1] Worked on this work_package') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [1], c.work_package_ids.sort |
||||
end |
||||
|
||||
it 'should ref keywords allow brackets around multiple work package numbers' do |
||||
Setting.commit_ref_keywords = '*' |
||||
|
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: '[#1 #2, #3] Worked on these') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [1, 2, 3], c.work_package_ids.sort |
||||
end |
||||
|
||||
it 'should commit referencing a subproject work package' do |
||||
c = Changeset.new(repository: Project.find(1).repository, |
||||
committed_on: Time.now, |
||||
comments: 'refs #5, a subproject work_package') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [5], c.work_package_ids.sort |
||||
assert c.work_packages.first.project != c.project |
||||
end |
||||
|
||||
it 'should commit referencing a parent project work package' do |
||||
# repository of child project |
||||
r = Repository::Subversion.create!( |
||||
project: Project.find(3), |
||||
scm_type: 'existing', |
||||
url: 'svn://localhost/test') |
||||
|
||||
c = Changeset.new(repository: r, |
||||
committed_on: Time.now, |
||||
comments: 'refs #2, an work_package of a parent project') |
||||
c.scan_comment_for_work_package_ids |
||||
|
||||
assert_equal [2], c.work_package_ids.sort |
||||
assert c.work_packages.first.project != c.project |
||||
end |
||||
|
||||
it 'should text tag revision' do |
||||
c = Changeset.new(revision: '520') |
||||
assert_equal 'r520', c.text_tag |
||||
end |
||||
|
||||
it 'should text tag hash' do |
||||
c = Changeset.new( |
||||
scmid: '7234cb2750b63f47bff735edc50a1c0a433c2518', |
||||
revision: '7234cb2750b63f47bff735edc50a1c0a433c2518') |
||||
assert_equal 'commit:7234cb2750b63f47bff735edc50a1c0a433c2518', c.text_tag |
||||
end |
||||
|
||||
it 'should text tag hash all number' do |
||||
c = Changeset.new(scmid: '0123456789', revision: '0123456789') |
||||
assert_equal 'commit:0123456789', c.text_tag |
||||
end |
||||
|
||||
it 'should previous' do |
||||
changeset = Changeset.find_by(revision: '3') |
||||
assert_equal Changeset.find_by(revision: '2'), changeset.previous |
||||
end |
||||
|
||||
it 'should previous nil' do |
||||
changeset = Changeset.find_by(revision: '1') |
||||
assert_nil changeset.previous |
||||
end |
||||
|
||||
it 'should next' do |
||||
changeset = Changeset.find_by(revision: '2') |
||||
assert_equal Changeset.find_by(revision: '3'), changeset.next |
||||
end |
||||
|
||||
it 'should next nil' do |
||||
changeset = Changeset.find_by(revision: '10') |
||||
assert_nil changeset.next |
||||
end |
||||
end |
||||
|
||||
context 'enabled scm', with_settings: { enabled_scm: ['subversion'] } do |
||||
it 'should comments empty' do |
||||
r = FactoryBot.create(:repository_subversion) |
||||
|
||||
assert r |
||||
c = Changeset.new(repository: r, |
||||
committed_on: Time.now, |
||||
revision: '123', |
||||
scmid: '12345', |
||||
comments: '') |
||||
assert(c.save) |
||||
assert_equal '', c.comments |
||||
if c.comments.respond_to?(:force_encoding) |
||||
assert_equal 'UTF-8', c.comments.encoding.to_s |
||||
end |
||||
end |
||||
|
||||
it 'should comments nil' do |
||||
r = FactoryBot.create(:repository_subversion) |
||||
assert r |
||||
|
||||
c = Changeset.new(repository: r, |
||||
committed_on: Time.now, |
||||
revision: '123', |
||||
scmid: '12345', |
||||
comments: nil) |
||||
assert(c.save) |
||||
assert_equal '', c.comments |
||||
if c.comments.respond_to?(:force_encoding) |
||||
assert_equal 'UTF-8', c.comments.encoding.to_s |
||||
end |
||||
end |
||||
|
||||
it 'should identifier' do |
||||
c = Changeset.find_by(revision: '1') |
||||
assert_equal c.revision, c.identifier |
||||
end |
||||
end |
||||
end |
@ -1,168 +0,0 @@ |
||||
#-- encoding: UTF-8 |
||||
|
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# 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. |
||||
# |
||||
# See docs/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
require_relative '../legacy_spec_helper' |
||||
|
||||
describe Repository, type: :model do |
||||
fixtures :all |
||||
|
||||
before do |
||||
@repository = Project.find(1).repository |
||||
Setting.enabled_scm = %w(subversion) |
||||
end |
||||
|
||||
it 'should create' do |
||||
repository = Repository::Subversion.new(project: Project.find(3), scm_type: 'existing') |
||||
assert !repository.save |
||||
|
||||
repository.url = 'svn://localhost' |
||||
assert repository.save |
||||
repository.reload |
||||
|
||||
project = Project.find(3) |
||||
assert_equal repository, project.repository |
||||
end |
||||
|
||||
it 'should destroy' do |
||||
changesets = Changeset.where('repository_id = 10').size |
||||
changes = Change.includes(:changeset) |
||||
.where("#{Changeset.table_name}.repository_id = 10") |
||||
.references(:changesets) |
||||
.size |
||||
assert_difference 'Changeset.count', -changesets do |
||||
assert_difference 'Change.count', -changes do |
||||
Repository.find(10).destroy |
||||
end |
||||
end |
||||
end |
||||
|
||||
it 'should not create with disabled scm' do |
||||
Setting.enabled_scm = ['Git'] # disable Subversion |
||||
repository = Repository::Subversion.new(project: Project.find(3), scm_type: 'existing', url: 'svn://localhost') |
||||
assert !repository.save |
||||
assert_includes repository.errors[:type], I18n.translate('activerecord.errors.models.repository.not_available') |
||||
# re-enable Subversion for following tests |
||||
Setting.delete_all |
||||
end |
||||
|
||||
it 'should scan changesets for work package ids' do |
||||
WorkPackage.all.each(&:recreate_initial_journal!) |
||||
|
||||
Setting.default_language = 'en' |
||||
Setting.notified_events = ['work_package_added', 'work_package_updated'] |
||||
|
||||
# choosing a status to apply to fix issues |
||||
Setting.commit_fix_status_id = Status.where(['is_closed = ?', true]).first.id |
||||
Setting.commit_fix_done_ratio = '90' |
||||
Setting.commit_ref_keywords = 'refs , references, IssueID' |
||||
Setting.commit_fix_keywords = 'fixes , closes' |
||||
Setting.default_language = 'en' |
||||
|
||||
# make sure work package 1 is not already closed |
||||
fixed_work_package = WorkPackage.find(1) |
||||
assert !fixed_work_package.status.is_closed? |
||||
old_status = fixed_work_package.status |
||||
|
||||
Repository.scan_changesets_for_work_package_ids |
||||
assert_equal [101, 102], WorkPackage.find(3).changeset_ids |
||||
|
||||
# fixed issues |
||||
fixed_work_package.reload |
||||
assert fixed_work_package.status.is_closed? |
||||
assert_equal 90, fixed_work_package.done_ratio |
||||
assert_equal [101], fixed_work_package.changeset_ids |
||||
|
||||
# issue change |
||||
journal = fixed_work_package.journals.last |
||||
assert_equal User.find_by_login('dlopper'), journal.user |
||||
assert_equal 'Applied in changeset r2.', journal.notes |
||||
|
||||
# 2 email notifications to 5 users |
||||
assert_equal 5, ActionMailer::Base.deliveries.size |
||||
mail = ActionMailer::Base.deliveries.first |
||||
assert_kind_of Mail::Message, mail |
||||
assert mail.subject.starts_with?("#{fixed_work_package.project.name} - #{fixed_work_package.status.name} #{fixed_work_package.type.name} ##{fixed_work_package.id}") |
||||
assert mail.body.encoded.include?( |
||||
"<strong>Status</strong> changed from <i title=\"#{old_status}\">#{old_status}</i> <br/><strong>to</strong> <i title=\"#{fixed_work_package.status}\">#{fixed_work_package.status}</i>" |
||||
) |
||||
|
||||
# ignoring commits referencing an issue of another project |
||||
assert_equal [], WorkPackage.find(4).changesets |
||||
end |
||||
|
||||
it 'should for changeset comments strip' do |
||||
repository = Repository::Subversion.create( |
||||
project: Project.find(4), |
||||
scm_type: 'existing', |
||||
url: 'svn://:login:password@host:/path/to/the/repository' |
||||
) |
||||
comment = 'This is a looooooooooooooong comment' + (' ' * 80 + "\n") * 5 |
||||
changeset = Changeset.new( |
||||
comments: comment, commit_date: Time.now, revision: 0, scmid: 'f39b7922fb3c', |
||||
committer: 'foo <foo@example.com>', committed_on: Time.now, repository: repository) |
||||
assert(changeset.save) |
||||
refute_equal(comment, changeset.comments) |
||||
assert_equal('This is a looooooooooooooong comment', changeset.comments) |
||||
end |
||||
|
||||
it 'should for urls strip' do |
||||
repository = Repository::Subversion.create( |
||||
project: Project.find(4), |
||||
url: ' svn://:login:password@host:/path/to/the/repository', |
||||
scm_type: 'existing', |
||||
log_encoding: 'UTF-8') |
||||
repository.root_url = 'foo ' # can't mass-assign this attr |
||||
assert repository.save |
||||
repository.reload |
||||
assert_equal 'svn://:login:password@host:/path/to/the/repository', repository.url |
||||
assert_equal 'foo', repository.root_url |
||||
end |
||||
|
||||
it 'should manual user mapping' do |
||||
assert_no_difference "Changeset.where('user_id <> 2').count" do |
||||
c = Changeset.create!(repository: @repository, committer: 'foo', committed_on: Time.now, revision: 100, comments: 'Committed by foo.') |
||||
assert_nil c.user |
||||
@repository.committer_ids = { 'foo' => '2' } |
||||
assert_equal User.find(2), c.reload.user |
||||
# committer is now mapped |
||||
c = Changeset.create!(repository: @repository, committer: 'foo', committed_on: Time.now, revision: 101, comments: 'Another commit by foo.') |
||||
assert_equal User.find(2), c.user |
||||
end |
||||
end |
||||
|
||||
it 'should auto user mapping by username' do |
||||
c = Changeset.create!(repository: @repository, committer: 'jsmith', committed_on: Time.now, revision: 100, comments: 'Committed by john.') |
||||
assert_equal User.find(2), c.user |
||||
end |
||||
|
||||
it 'should auto user mapping by email' do |
||||
c = Changeset.create!(repository: @repository, committer: 'john <jsmith@somenet.foo>', committed_on: Time.now, revision: 100, comments: 'Committed by john.') |
||||
assert_equal User.find(2), c.user |
||||
end |
||||
end |
Loading…
Reference in new issue