2011-10-08 17:36:38 -04:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2017-07-10 10:24:02 -04:00
|
|
|
describe Project do
|
2015-05-02 23:11:21 -04:00
|
|
|
describe 'associations' do
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to belong_to(:group) }
|
|
|
|
it { is_expected.to belong_to(:namespace) }
|
|
|
|
it { is_expected.to belong_to(:creator).class_name('User') }
|
|
|
|
it { is_expected.to have_many(:users) }
|
2016-09-02 08:15:18 -04:00
|
|
|
it { is_expected.to have_many(:services) }
|
2017-05-30 07:54:59 -04:00
|
|
|
it { is_expected.to have_many(:events) }
|
|
|
|
it { is_expected.to have_many(:merge_requests) }
|
|
|
|
it { is_expected.to have_many(:issues) }
|
|
|
|
it { is_expected.to have_many(:milestones) }
|
|
|
|
it { is_expected.to have_many(:project_members).dependent(:delete_all) }
|
2016-06-27 10:20:57 -04:00
|
|
|
it { is_expected.to have_many(:users).through(:project_members) }
|
2017-05-30 07:54:59 -04:00
|
|
|
it { is_expected.to have_many(:requesters).dependent(:delete_all) }
|
|
|
|
it { is_expected.to have_many(:notes) }
|
|
|
|
it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') }
|
|
|
|
it { is_expected.to have_many(:deploy_keys_projects) }
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to have_many(:deploy_keys) }
|
2017-05-30 07:54:59 -04:00
|
|
|
it { is_expected.to have_many(:hooks) }
|
|
|
|
it { is_expected.to have_many(:protected_branches) }
|
|
|
|
it { is_expected.to have_one(:forked_project_link) }
|
|
|
|
it { is_expected.to have_one(:slack_service) }
|
|
|
|
it { is_expected.to have_one(:microsoft_teams_service) }
|
|
|
|
it { is_expected.to have_one(:mattermost_service) }
|
|
|
|
it { is_expected.to have_one(:pushover_service) }
|
|
|
|
it { is_expected.to have_one(:asana_service) }
|
|
|
|
it { is_expected.to have_many(:boards) }
|
|
|
|
it { is_expected.to have_one(:campfire_service) }
|
|
|
|
it { is_expected.to have_one(:drone_ci_service) }
|
|
|
|
it { is_expected.to have_one(:emails_on_push_service) }
|
|
|
|
it { is_expected.to have_one(:pipelines_email_service) }
|
|
|
|
it { is_expected.to have_one(:irker_service) }
|
|
|
|
it { is_expected.to have_one(:pivotaltracker_service) }
|
|
|
|
it { is_expected.to have_one(:hipchat_service) }
|
|
|
|
it { is_expected.to have_one(:flowdock_service) }
|
|
|
|
it { is_expected.to have_one(:assembla_service) }
|
|
|
|
it { is_expected.to have_one(:slack_slash_commands_service) }
|
|
|
|
it { is_expected.to have_one(:mattermost_slash_commands_service) }
|
|
|
|
it { is_expected.to have_one(:gemnasium_service) }
|
|
|
|
it { is_expected.to have_one(:buildkite_service) }
|
|
|
|
it { is_expected.to have_one(:bamboo_service) }
|
|
|
|
it { is_expected.to have_one(:teamcity_service) }
|
|
|
|
it { is_expected.to have_one(:jira_service) }
|
|
|
|
it { is_expected.to have_one(:redmine_service) }
|
|
|
|
it { is_expected.to have_one(:custom_issue_tracker_service) }
|
|
|
|
it { is_expected.to have_one(:bugzilla_service) }
|
|
|
|
it { is_expected.to have_one(:gitlab_issue_tracker_service) }
|
|
|
|
it { is_expected.to have_one(:external_wiki_service) }
|
|
|
|
it { is_expected.to have_one(:project_feature) }
|
|
|
|
it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
|
|
|
|
it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
|
2016-09-02 08:15:18 -04:00
|
|
|
it { is_expected.to have_one(:last_event).class_name('Event') }
|
|
|
|
it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
|
2017-09-04 09:44:46 -04:00
|
|
|
it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
|
2015-12-10 11:29:44 -05:00
|
|
|
it { is_expected.to have_many(:commit_statuses) }
|
2016-06-03 08:46:17 -04:00
|
|
|
it { is_expected.to have_many(:pipelines) }
|
2015-12-10 11:44:06 -05:00
|
|
|
it { is_expected.to have_many(:builds) }
|
|
|
|
it { is_expected.to have_many(:runner_projects) }
|
|
|
|
it { is_expected.to have_many(:runners) }
|
2017-04-06 15:57:04 -04:00
|
|
|
it { is_expected.to have_many(:active_runners) }
|
2015-12-10 11:44:06 -05:00
|
|
|
it { is_expected.to have_many(:variables) }
|
|
|
|
it { is_expected.to have_many(:triggers) }
|
2016-02-12 10:05:17 -05:00
|
|
|
it { is_expected.to have_many(:pages_domains) }
|
2017-05-30 07:54:59 -04:00
|
|
|
it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
|
|
|
|
it { is_expected.to have_many(:users_star_projects) }
|
|
|
|
it { is_expected.to have_many(:environments) }
|
|
|
|
it { is_expected.to have_many(:deployments) }
|
|
|
|
it { is_expected.to have_many(:todos) }
|
|
|
|
it { is_expected.to have_many(:releases) }
|
|
|
|
it { is_expected.to have_many(:lfs_objects_projects) }
|
|
|
|
it { is_expected.to have_many(:project_group_links) }
|
|
|
|
it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
|
2016-09-02 08:15:18 -04:00
|
|
|
it { is_expected.to have_many(:forks).through(:forked_project_links) }
|
2017-03-02 12:02:33 -05:00
|
|
|
it { is_expected.to have_many(:uploads).dependent(:destroy) }
|
2017-05-30 07:54:59 -04:00
|
|
|
it { is_expected.to have_many(:pipeline_schedules) }
|
2017-09-05 12:03:24 -04:00
|
|
|
it { is_expected.to have_many(:members_and_requesters) }
|
2016-06-27 10:20:57 -04:00
|
|
|
|
2016-10-28 16:56:13 -04:00
|
|
|
context 'after initialized' do
|
|
|
|
it "has a project_feature" do
|
2017-07-25 13:09:00 -04:00
|
|
|
expect(described_class.new.project_feature).to be_present
|
2016-10-14 18:38:41 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-27 10:20:57 -04:00
|
|
|
describe '#members & #requesters' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, :public, :access_requestable) }
|
2016-06-27 10:20:57 -04:00
|
|
|
let(:requester) { create(:user) }
|
|
|
|
let(:developer) { create(:user) }
|
|
|
|
before do
|
|
|
|
project.request_access(requester)
|
|
|
|
project.team << [developer, :developer]
|
|
|
|
end
|
|
|
|
|
2017-09-05 12:03:24 -04:00
|
|
|
it_behaves_like 'members and requesters associations' do
|
|
|
|
let(:namespace) { project }
|
2016-06-27 10:20:57 -04:00
|
|
|
end
|
|
|
|
end
|
2016-10-06 14:58:28 -04:00
|
|
|
|
|
|
|
describe '#boards' do
|
|
|
|
it 'raises an error when attempting to add more than one board to the project' do
|
|
|
|
subject.boards.build
|
|
|
|
|
2016-10-10 10:22:30 -04:00
|
|
|
expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
|
2016-10-06 14:58:28 -04:00
|
|
|
expect(subject.boards.size).to eq 1
|
|
|
|
end
|
|
|
|
end
|
2011-10-08 17:36:38 -04:00
|
|
|
end
|
|
|
|
|
2015-05-02 23:11:21 -04:00
|
|
|
describe 'modules' do
|
|
|
|
subject { described_class }
|
|
|
|
|
|
|
|
it { is_expected.to include_module(Gitlab::ConfigHelper) }
|
|
|
|
it { is_expected.to include_module(Gitlab::ShellAdapter) }
|
|
|
|
it { is_expected.to include_module(Gitlab::VisibilityLevel) }
|
2016-08-01 05:06:57 -04:00
|
|
|
it { is_expected.to include_module(Gitlab::CurrentSettings) }
|
2015-05-02 23:11:21 -04:00
|
|
|
it { is_expected.to include_module(Referable) }
|
|
|
|
it { is_expected.to include_module(Sortable) }
|
2012-09-26 14:17:17 -04:00
|
|
|
end
|
|
|
|
|
2015-05-02 23:11:21 -04:00
|
|
|
describe 'validation' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project) }
|
2012-08-29 11:36:02 -04:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to validate_presence_of(:name) }
|
|
|
|
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
|
2016-12-02 07:54:57 -05:00
|
|
|
it { is_expected.to validate_length_of(:name).is_at_most(255) }
|
2012-08-29 11:36:02 -04:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to validate_presence_of(:path) }
|
|
|
|
it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
|
2016-12-02 07:54:57 -05:00
|
|
|
it { is_expected.to validate_length_of(:path).is_at_most(255) }
|
|
|
|
|
|
|
|
it { is_expected.to validate_length_of(:description).is_at_most(2000) }
|
|
|
|
|
2017-07-05 08:11:01 -04:00
|
|
|
it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
|
|
|
|
it { is_expected.to allow_value('').for(:ci_config_path) }
|
|
|
|
it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
|
2017-06-28 03:53:12 -04:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to validate_presence_of(:creator) }
|
2016-12-02 07:54:57 -05:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to validate_presence_of(:namespace) }
|
2016-12-02 07:54:57 -05:00
|
|
|
|
2016-06-22 17:04:51 -04:00
|
|
|
it { is_expected.to validate_presence_of(:repository_storage) }
|
2012-08-29 11:36:02 -04:00
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'does not allow new projects beyond user limits' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project)
|
2015-02-12 13:17:35 -05:00
|
|
|
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
|
|
|
|
expect(project2).not_to be_valid
|
2016-05-25 03:52:13 -04:00
|
|
|
expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
|
2012-08-29 11:36:02 -04:00
|
|
|
end
|
2016-06-27 14:23:19 -04:00
|
|
|
|
|
|
|
describe 'wiki path conflict' do
|
|
|
|
context "when the new path has been used by the wiki of other Project" do
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'has an error on the name attribute' do
|
2017-08-02 15:55:11 -04:00
|
|
|
new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
|
2016-06-27 14:23:19 -04:00
|
|
|
|
|
|
|
expect(new_project).not_to be_valid
|
|
|
|
expect(new_project.errors[:name].first).to eq('has already been taken')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when the new wiki path has been used by the path of other Project" do
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'has an error on the name attribute' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project_with_wiki_suffix = create(:project, path: 'foo.wiki')
|
|
|
|
new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
|
2016-06-27 14:23:19 -04:00
|
|
|
|
|
|
|
expect(new_project).not_to be_valid
|
|
|
|
expect(new_project.errors[:name].first).to eq('has already been taken')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-06-22 17:04:51 -04:00
|
|
|
|
2017-08-22 19:19:35 -04:00
|
|
|
context 'repository storages inclusion' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project2) { build(:project, repository_storage: 'missing') }
|
2016-06-22 17:04:51 -04:00
|
|
|
|
|
|
|
before do
|
2017-02-28 16:08:40 -05:00
|
|
|
storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
|
2016-06-22 17:04:51 -04:00
|
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
|
|
end
|
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it "does not allow repository storages that don't match a label in the configuration" do
|
2016-06-22 17:04:51 -04:00
|
|
|
expect(project2).not_to be_valid
|
|
|
|
expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
|
|
|
|
end
|
|
|
|
end
|
2016-06-30 08:37:48 -04:00
|
|
|
|
2016-07-12 10:21:28 -04:00
|
|
|
it 'does not allow an invalid URI as import_url' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: 'invalid://')
|
2016-06-22 10:39:16 -04:00
|
|
|
|
|
|
|
expect(project2).not_to be_valid
|
|
|
|
end
|
|
|
|
|
2016-07-12 10:21:28 -04:00
|
|
|
it 'does allow a valid URI as import_url' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
|
2016-06-22 10:39:16 -04:00
|
|
|
|
2016-06-17 09:09:39 -04:00
|
|
|
expect(project2).to be_valid
|
|
|
|
end
|
2016-07-12 10:21:28 -04:00
|
|
|
|
2016-07-17 23:12:32 -04:00
|
|
|
it 'allows an empty URI' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: '')
|
2016-07-12 10:21:28 -04:00
|
|
|
|
2016-07-17 23:12:32 -04:00
|
|
|
expect(project2).to be_valid
|
2016-07-12 10:21:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not produce import data on an empty URI' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: '')
|
2016-07-12 10:21:28 -04:00
|
|
|
|
|
|
|
expect(project2.import_data).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not produce import data on an invalid URI' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: 'test://')
|
2016-07-12 10:21:28 -04:00
|
|
|
|
|
|
|
expect(project2.import_data).to be_nil
|
|
|
|
end
|
2016-07-21 05:40:49 -04:00
|
|
|
|
2017-03-15 16:09:08 -04:00
|
|
|
it "does not allow blocked import_url localhost" do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: 'http://localhost:9000/t.git')
|
2017-03-15 16:09:08 -04:00
|
|
|
|
|
|
|
expect(project2).to be_invalid
|
|
|
|
expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not allow blocked import_url port" do
|
2017-08-02 15:55:11 -04:00
|
|
|
project2 = build(:project, import_url: 'http://github.com:25/t.git')
|
2017-03-15 16:09:08 -04:00
|
|
|
|
|
|
|
expect(project2).to be_invalid
|
|
|
|
expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
|
|
|
|
end
|
|
|
|
|
2016-07-21 05:40:49 -04:00
|
|
|
describe 'project pending deletion' do
|
|
|
|
let!(:project_pending_deletion) do
|
2017-08-02 15:55:11 -04:00
|
|
|
create(:project,
|
2016-07-21 05:40:49 -04:00
|
|
|
pending_delete: true)
|
|
|
|
end
|
|
|
|
let(:new_project) do
|
2017-08-02 15:55:11 -04:00
|
|
|
build(:project,
|
2016-07-21 05:40:49 -04:00
|
|
|
name: project_pending_deletion.name,
|
|
|
|
namespace: project_pending_deletion.namespace)
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
|
|
|
new_project.validate
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'contains errors related to the project being deleted' do
|
|
|
|
expect(new_project.errors.full_messages.first).to eq('The project is still being deleted. Please try again later.')
|
|
|
|
end
|
|
|
|
end
|
2017-04-05 09:41:00 -04:00
|
|
|
|
|
|
|
describe 'path validation' do
|
|
|
|
it 'allows paths reserved on the root namespace' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = build(:project, path: 'api')
|
2017-04-05 09:41:00 -04:00
|
|
|
|
|
|
|
expect(project).to be_valid
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'rejects paths reserved on another level' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = build(:project, path: 'tree')
|
2017-04-05 09:41:00 -04:00
|
|
|
|
|
|
|
expect(project).not_to be_valid
|
|
|
|
end
|
2017-04-18 10:27:11 -04:00
|
|
|
|
|
|
|
it 'rejects nested paths' do
|
|
|
|
parent = create(:group, :nested, path: 'environments')
|
2017-08-02 15:55:11 -04:00
|
|
|
project = build(:project, path: 'folders', namespace: parent)
|
2017-04-18 10:27:11 -04:00
|
|
|
|
|
|
|
expect(project).not_to be_valid
|
|
|
|
end
|
2017-04-28 12:09:01 -04:00
|
|
|
|
|
|
|
it 'allows a reserved group name' do
|
|
|
|
parent = create(:group)
|
2017-08-02 15:55:11 -04:00
|
|
|
project = build(:project, path: 'avatar', namespace: parent)
|
2017-04-28 12:09:01 -04:00
|
|
|
|
|
|
|
expect(project).to be_valid
|
|
|
|
end
|
2017-04-05 09:41:00 -04:00
|
|
|
end
|
2011-10-08 17:36:38 -04:00
|
|
|
end
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2015-12-04 06:55:23 -05:00
|
|
|
describe 'project token' do
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'sets an random token if none provided' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = FactoryGirl.create :project, runners_token: ''
|
2015-12-11 07:39:43 -05:00
|
|
|
expect(project.runners_token).not_to eq('')
|
2015-12-04 06:55:23 -05:00
|
|
|
end
|
|
|
|
|
2016-09-02 08:15:18 -04:00
|
|
|
it 'does not set an random token if one provided' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = FactoryGirl.create :project, runners_token: 'my-token'
|
2015-12-11 07:39:43 -05:00
|
|
|
expect(project.runners_token).to eq('my-token')
|
2015-12-04 06:55:23 -05:00
|
|
|
end
|
|
|
|
end
|
2011-10-08 17:36:38 -04:00
|
|
|
|
2015-01-19 15:37:20 -05:00
|
|
|
describe 'Respond to' do
|
2015-02-12 13:17:35 -05:00
|
|
|
it { is_expected.to respond_to(:url_to_repo) }
|
|
|
|
it { is_expected.to respond_to(:repo_exists?) }
|
|
|
|
it { is_expected.to respond_to(:execute_hooks) }
|
|
|
|
it { is_expected.to respond_to(:owner) }
|
|
|
|
it { is_expected.to respond_to(:path_with_namespace) }
|
2017-07-20 05:34:09 -04:00
|
|
|
it { is_expected.to respond_to(:full_path) }
|
2011-10-08 17:36:38 -04:00
|
|
|
end
|
|
|
|
|
2016-11-18 07:52:39 -05:00
|
|
|
describe 'delegation' do
|
2017-07-20 11:12:06 -04:00
|
|
|
[:add_guest, :add_reporter, :add_developer, :add_master, :add_user, :add_users].each do |method|
|
|
|
|
it { is_expected.to delegate_method(method).to(:team) }
|
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to delegate_method(:empty_repo?).to(:repository) }
|
|
|
|
it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
|
|
|
|
it { is_expected.to delegate_method(:count).to(:forks).with_prefix(true) }
|
|
|
|
it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
|
2016-11-18 07:52:39 -05:00
|
|
|
end
|
|
|
|
|
2015-05-02 23:11:21 -04:00
|
|
|
describe '#to_reference' do
|
2016-12-15 09:51:50 -05:00
|
|
|
let(:owner) { create(:user, name: 'Gitlab') }
|
2016-11-02 19:49:13 -04:00
|
|
|
let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, path: 'sample-project', namespace: namespace) }
|
2016-12-15 09:51:50 -05:00
|
|
|
let(:group) { create(:group, name: 'Group', path: 'sample-group', owner: owner) }
|
2015-05-02 23:11:21 -04:00
|
|
|
|
2016-11-02 19:49:13 -04:00
|
|
|
context 'when nil argument' do
|
2016-12-15 09:51:50 -05:00
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.to_reference).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-10 18:52:25 -05:00
|
|
|
context 'when full is true' do
|
2016-12-15 08:06:08 -05:00
|
|
|
it 'returns complete path to the project' do
|
2017-01-10 18:52:25 -05:00
|
|
|
expect(project.to_reference(full: true)).to eq 'sample-namespace/sample-project'
|
|
|
|
expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project'
|
|
|
|
expect(project.to_reference(group, full: true)).to eq 'sample-namespace/sample-project'
|
2016-11-02 19:49:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when same project argument' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.to_reference(project)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when cross namespace project argument' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:another_namespace_project) { create(:project, name: 'another-project') }
|
2016-11-02 19:49:13 -04:00
|
|
|
|
|
|
|
it 'returns complete path to the project' do
|
|
|
|
expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when same namespace / cross-project argument' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:another_project) { create(:project, namespace: namespace) }
|
2016-11-02 19:49:13 -04:00
|
|
|
|
2016-12-15 08:06:08 -05:00
|
|
|
it 'returns path to the project' do
|
2016-11-02 19:49:13 -04:00
|
|
|
expect(project.to_reference(another_project)).to eq 'sample-project'
|
|
|
|
end
|
|
|
|
end
|
2016-12-15 08:06:08 -05:00
|
|
|
|
2016-12-15 09:51:50 -05:00
|
|
|
context 'when different namespace / cross-project argument' do
|
|
|
|
let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:another_project) { create(:project, path: 'another-project', namespace: another_namespace) }
|
2016-12-15 09:51:50 -05:00
|
|
|
|
|
|
|
it 'returns full path to the project' do
|
|
|
|
expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when argument is a namespace' do
|
|
|
|
context 'with same project path' do
|
|
|
|
it 'returns path to the project' do
|
|
|
|
expect(project.to_reference(namespace)).to eq 'sample-project'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with different project path' do
|
|
|
|
it 'returns full path to the project' do
|
|
|
|
expect(project.to_reference(group)).to eq 'sample-namespace/sample-project'
|
|
|
|
end
|
2016-12-15 08:06:08 -05:00
|
|
|
end
|
|
|
|
end
|
2016-11-02 19:49:13 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
describe '#to_human_reference' do
|
|
|
|
let(:owner) { create(:user, name: 'Gitlab') }
|
|
|
|
let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, name: 'Sample project', namespace: namespace) }
|
2016-11-02 19:49:13 -04:00
|
|
|
|
|
|
|
context 'when nil argument' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.to_human_reference).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when same project argument' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.to_human_reference(project)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when cross namespace project argument' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:another_namespace_project) { create(:project, name: 'another-project') }
|
2016-11-02 19:49:13 -04:00
|
|
|
|
|
|
|
it 'returns complete name with namespace of the project' do
|
|
|
|
expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when same namespace / cross-project argument' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:another_project) { create(:project, namespace: namespace) }
|
2016-11-02 19:49:13 -04:00
|
|
|
|
|
|
|
it 'returns name of the project' do
|
|
|
|
expect(project.to_human_reference(another_project)).to eq 'Sample project'
|
|
|
|
end
|
2015-05-02 23:11:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-20 06:31:39 -04:00
|
|
|
describe '#merge_method' do
|
|
|
|
it 'returns "ff" merge_method when ff is enabled' do
|
|
|
|
project = build(:project, merge_requests_ff_only_enabled: true)
|
|
|
|
expect(project.merge_method).to be :ff
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns "merge" merge_method when ff is disabled' do
|
|
|
|
project = build(:project, merge_requests_ff_only_enabled: false)
|
|
|
|
expect(project.merge_method).to be :merge
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-06-22 17:04:51 -04:00
|
|
|
describe '#repository_storage_path' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, repository_storage: 'custom') }
|
2016-06-22 17:04:51 -04:00
|
|
|
|
|
|
|
before do
|
|
|
|
FileUtils.mkdir('tmp/tests/custom_repositories')
|
2017-02-28 16:08:40 -05:00
|
|
|
storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
|
2016-06-22 17:04:51 -04:00
|
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
|
|
end
|
|
|
|
|
|
|
|
after do
|
|
|
|
FileUtils.rm_rf('tmp/tests/custom_repositories')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the repository storage path' do
|
|
|
|
expect(project.repository_storage_path).to eq('tmp/tests/custom_repositories')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'returns valid url to repo' do
|
2017-07-25 13:09:00 -04:00
|
|
|
project = described_class.new(path: 'somewhere')
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
|
2011-10-08 17:36:38 -04:00
|
|
|
end
|
|
|
|
|
2015-07-29 07:23:28 -04:00
|
|
|
describe "#web_url" do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, path: "somewhere") }
|
2015-07-29 07:23:28 -04:00
|
|
|
|
|
|
|
it 'returns the full web URL for this repo' do
|
2017-02-23 18:55:01 -05:00
|
|
|
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
|
2015-07-29 07:23:28 -04:00
|
|
|
end
|
2011-12-14 11:38:52 -05:00
|
|
|
end
|
|
|
|
|
2016-10-18 14:03:31 -04:00
|
|
|
describe "#new_issue_address" do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, path: "somewhere") }
|
2016-06-23 06:36:44 -04:00
|
|
|
let(:user) { create(:user) }
|
|
|
|
|
2016-06-24 04:05:05 -04:00
|
|
|
context 'incoming email enabled' do
|
|
|
|
before do
|
|
|
|
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the address to create a new issue' do
|
2017-07-20 05:34:09 -04:00
|
|
|
address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
|
2016-06-24 04:05:05 -04:00
|
|
|
|
|
|
|
expect(project.new_issue_address(user)).to eq(address)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'incoming email disabled' do
|
|
|
|
before do
|
|
|
|
stub_incoming_email_setting(enabled: false)
|
|
|
|
end
|
2016-06-23 06:36:44 -04:00
|
|
|
|
2016-06-24 04:05:05 -04:00
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.new_issue_address(user)).to be_nil
|
|
|
|
end
|
2016-06-23 06:36:44 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-01-19 15:37:20 -05:00
|
|
|
describe 'last_activity methods' do
|
2016-10-06 18:50:29 -04:00
|
|
|
let(:timestamp) { 2.hours.ago }
|
|
|
|
# last_activity_at gets set to created_at upon creation
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
|
2011-11-02 16:14:03 -04:00
|
|
|
|
2015-01-19 15:37:20 -05:00
|
|
|
describe 'last_activity' do
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'alias last_activity to last_event' do
|
Migrate events into a new format
This commit migrates events data in such a way that push events are
stored much more efficiently. This is done by creating a shadow table
called "events_for_migration", and a table called "push_event_payloads"
which is used for storing push data of push events. The background
migration in this commit will copy events from the "events" table into
the "events_for_migration" table, push events in will also have a row
created in "push_event_payloads".
This approach allows us to reclaim space in the next release by simply
swapping the "events" and "events_for_migration" tables, then dropping
the old events (now "events_for_migration") table.
The new table structure is also optimised for storage space, and does
not include the unused "title" column nor the "data" column (since this
data is moved to "push_event_payloads").
== Newly Created Events
Newly created events are inserted into both "events" and
"events_for_migration", both using the exact same primary key value. The
table "push_event_payloads" in turn has a foreign key to the _shadow_
table. This removes the need for recreating and validating the foreign
key after swapping the tables. Since the shadow table also has a foreign
key to "projects.id" we also don't have to worry about orphaned rows.
This approach however does require some additional storage as we're
duplicating a portion of the events data for at least 1 release. The
exact amount is hard to estimate, but for GitLab.com this is expected to
be between 10 and 20 GB at most. The background migration in this commit
deliberately does _not_ update the "events" table as doing so would put
a lot of pressure on PostgreSQL's auto vacuuming system.
== Supporting Both Old And New Events
Application code has also been adjusted to support push events using
both the old and new data formats. This is done by creating a PushEvent
class which extends the regular Event class. Using Rails' Single Table
Inheritance system we can ensure the right class is used for the right
data, which in this case is based on the value of `events.action`. To
support displaying old and new data at the same time the PushEvent class
re-defines a few methods of the Event class, falling back to their
original implementations for push events in the old format.
Once all existing events have been migrated the various push event
related methods can be removed from the Event model, and the calls to
`super` can be removed from the methods in the PushEvent model.
The UI and event atom feed have also been slightly changed to better
handle this new setup, fortunately only a few changes were necessary to
make this work.
== API Changes
The API only displays push data of events in the new format. Supporting
both formats in the API is a bit more difficult compared to the UI.
Since the old push data was not really well documented (apart from one
example that used an incorrect "action" nmae) I decided that supporting
both was not worth the effort, especially since events will be migrated
in a few days _and_ new events are created in the correct format.
2017-07-10 11:43:57 -04:00
|
|
|
last_event = create(:event, :closed, project: project)
|
2016-09-19 22:59:29 -04:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.last_activity).to eq(last_event)
|
2012-10-17 15:35:08 -04:00
|
|
|
end
|
2011-11-02 16:14:03 -04:00
|
|
|
end
|
|
|
|
|
2012-10-17 15:35:08 -04:00
|
|
|
describe 'last_activity_date' do
|
|
|
|
it 'returns the creation date of the project\'s last event if present' do
|
Migrate events into a new format
This commit migrates events data in such a way that push events are
stored much more efficiently. This is done by creating a shadow table
called "events_for_migration", and a table called "push_event_payloads"
which is used for storing push data of push events. The background
migration in this commit will copy events from the "events" table into
the "events_for_migration" table, push events in will also have a row
created in "push_event_payloads".
This approach allows us to reclaim space in the next release by simply
swapping the "events" and "events_for_migration" tables, then dropping
the old events (now "events_for_migration") table.
The new table structure is also optimised for storage space, and does
not include the unused "title" column nor the "data" column (since this
data is moved to "push_event_payloads").
== Newly Created Events
Newly created events are inserted into both "events" and
"events_for_migration", both using the exact same primary key value. The
table "push_event_payloads" in turn has a foreign key to the _shadow_
table. This removes the need for recreating and validating the foreign
key after swapping the tables. Since the shadow table also has a foreign
key to "projects.id" we also don't have to worry about orphaned rows.
This approach however does require some additional storage as we're
duplicating a portion of the events data for at least 1 release. The
exact amount is hard to estimate, but for GitLab.com this is expected to
be between 10 and 20 GB at most. The background migration in this commit
deliberately does _not_ update the "events" table as doing so would put
a lot of pressure on PostgreSQL's auto vacuuming system.
== Supporting Both Old And New Events
Application code has also been adjusted to support push events using
both the old and new data formats. This is done by creating a PushEvent
class which extends the regular Event class. Using Rails' Single Table
Inheritance system we can ensure the right class is used for the right
data, which in this case is based on the value of `events.action`. To
support displaying old and new data at the same time the PushEvent class
re-defines a few methods of the Event class, falling back to their
original implementations for push events in the old format.
Once all existing events have been migrated the various push event
related methods can be removed from the Event model, and the calls to
`super` can be removed from the methods in the PushEvent model.
The UI and event atom feed have also been slightly changed to better
handle this new setup, fortunately only a few changes were necessary to
make this work.
== API Changes
The API only displays push data of events in the new format. Supporting
both formats in the API is a bit more difficult compared to the UI.
Since the old push data was not really well documented (apart from one
example that used an incorrect "action" nmae) I decided that supporting
both was not worth the effort, especially since events will be migrated
in a few days _and_ new events are created in the correct format.
2017-07-10 11:43:57 -04:00
|
|
|
new_event = create(:event, :closed, project: project, created_at: Time.now)
|
2016-09-19 22:59:29 -04:00
|
|
|
|
2016-10-06 18:50:29 -04:00
|
|
|
project.reload
|
2016-09-19 22:59:29 -04:00
|
|
|
expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
|
2012-10-17 15:35:08 -04:00
|
|
|
end
|
2012-06-12 14:27:03 -04:00
|
|
|
|
2012-10-17 15:35:08 -04:00
|
|
|
it 'returns the project\'s last update date if it has no events' do
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.last_activity_date).to eq(project.updated_at)
|
2012-10-17 15:35:08 -04:00
|
|
|
end
|
2012-06-12 14:27:03 -04:00
|
|
|
end
|
|
|
|
end
|
2012-10-17 15:35:08 -04:00
|
|
|
|
2015-05-01 13:29:36 -04:00
|
|
|
describe '#get_issue' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2015-12-12 02:17:36 -05:00
|
|
|
let!(:issue) { create(:issue, project: project) }
|
2016-11-22 05:25:04 -05:00
|
|
|
let(:user) { create(:user) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
project.team << [user, :developer]
|
|
|
|
end
|
2015-05-01 13:29:36 -04:00
|
|
|
|
|
|
|
context 'with default issues tracker' do
|
|
|
|
it 'returns an issue' do
|
2016-11-22 05:25:04 -05:00
|
|
|
expect(project.get_issue(issue.iid, user)).to eq issue
|
2015-05-01 13:29:36 -04:00
|
|
|
end
|
|
|
|
|
2015-12-12 02:17:36 -05:00
|
|
|
it 'returns count of open issues' do
|
|
|
|
expect(project.open_issues_count).to eq(1)
|
|
|
|
end
|
|
|
|
|
2015-05-01 13:29:36 -04:00
|
|
|
it 'returns nil when no issue found' do
|
2016-11-22 05:25:04 -05:00
|
|
|
expect(project.get_issue(999, user)).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it "returns nil when user doesn't have access" do
|
|
|
|
user = create(:user)
|
|
|
|
expect(project.get_issue(issue.iid, user)).to eq nil
|
2015-05-01 13:29:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with external issues tracker' do
|
2017-07-10 03:38:42 -04:00
|
|
|
let!(:internal_issue) { create(:issue, project: project) }
|
2015-05-01 13:29:36 -04:00
|
|
|
before do
|
2017-07-10 03:38:42 -04:00
|
|
|
allow(project).to receive(:external_issue_tracker).and_return(true)
|
2015-05-01 13:29:36 -04:00
|
|
|
end
|
|
|
|
|
2017-07-10 03:38:42 -04:00
|
|
|
context 'when internal issues are enabled' do
|
|
|
|
it 'returns interlan issue' do
|
|
|
|
issue = project.get_issue(internal_issue.iid, user)
|
|
|
|
|
|
|
|
expect(issue).to be_kind_of(Issue)
|
|
|
|
expect(issue.iid).to eq(internal_issue.iid)
|
|
|
|
expect(issue.project).to eq(project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns an ExternalIssue when internal issue does not exists' do
|
|
|
|
issue = project.get_issue('FOO-1234', user)
|
|
|
|
|
|
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
|
|
expect(issue.iid).to eq('FOO-1234')
|
|
|
|
expect(issue.project).to eq(project)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when internal issues are disabled' do
|
|
|
|
before do
|
|
|
|
project.issues_enabled = false
|
|
|
|
project.save!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns always an External issues' do
|
|
|
|
issue = project.get_issue(internal_issue.iid, user)
|
|
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
|
|
expect(issue.iid).to eq(internal_issue.iid.to_s)
|
|
|
|
expect(issue.project).to eq(project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns an ExternalIssue when internal issue does not exists' do
|
|
|
|
issue = project.get_issue('FOO-1234', user)
|
|
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
|
|
expect(issue.iid).to eq('FOO-1234')
|
|
|
|
expect(issue.project).to eq(project)
|
|
|
|
end
|
2015-05-01 13:29:36 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#issue_exists?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2015-05-01 13:29:36 -04:00
|
|
|
|
|
|
|
it 'is truthy when issue exists' do
|
|
|
|
expect(project).to receive(:get_issue).and_return(double)
|
|
|
|
expect(project.issue_exists?(1)).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'is falsey when issue does not exist' do
|
|
|
|
expect(project).to receive(:get_issue).and_return(nil)
|
|
|
|
expect(project.issue_exists?(1)).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#to_param' do
|
2013-01-02 12:00:00 -05:00
|
|
|
context 'with namespace' do
|
|
|
|
before do
|
|
|
|
@group = create :group, name: 'gitlab'
|
2017-08-02 15:55:11 -04:00
|
|
|
@project = create(:project, name: 'gitlabhq', namespace: @group)
|
2013-01-02 12:00:00 -05:00
|
|
|
end
|
|
|
|
|
2015-01-24 13:02:58 -05:00
|
|
|
it { expect(@project.to_param).to eq('gitlabhq') }
|
2013-01-02 12:00:00 -05:00
|
|
|
end
|
2016-07-26 14:27:42 -04:00
|
|
|
|
|
|
|
context 'with invalid path' do
|
|
|
|
it 'returns previous path to keep project suitable for use in URLs when persisted' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, path: 'gitlab')
|
2016-07-26 14:27:42 -04:00
|
|
|
project.path = 'foo&bar'
|
|
|
|
|
|
|
|
expect(project).not_to be_valid
|
|
|
|
expect(project.to_param).to eq 'gitlab'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns current path when new record' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = build(:project, path: 'gitlab')
|
2016-07-26 14:27:42 -04:00
|
|
|
project.path = 'foo&bar'
|
|
|
|
|
|
|
|
expect(project).not_to be_valid
|
|
|
|
expect(project.to_param).to eq 'foo&bar'
|
|
|
|
end
|
|
|
|
end
|
2013-01-02 12:00:00 -05:00
|
|
|
end
|
2013-01-04 11:50:31 -05:00
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#repository' do
|
2017-01-19 16:16:33 -05:00
|
|
|
let(:project) { create(:project, :repository) }
|
2013-01-04 11:50:31 -05:00
|
|
|
|
2016-07-19 08:59:38 -04:00
|
|
|
it 'returns valid repo' do
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.repository).to be_kind_of(Repository)
|
2013-01-04 11:50:31 -05:00
|
|
|
end
|
|
|
|
end
|
2013-02-11 06:41:12 -05:00
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#default_issues_tracker?' do
|
2016-08-01 11:00:44 -04:00
|
|
|
it "is true if used internal tracker" do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = build(:project)
|
2017-01-19 16:16:33 -05:00
|
|
|
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.default_issues_tracker?).to be_truthy
|
2013-02-11 06:41:12 -05:00
|
|
|
end
|
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it "is false if used other tracker" do
|
2017-01-19 16:16:33 -05:00
|
|
|
# NOTE: The current nature of this factory requires persistence
|
|
|
|
project = create(:redmine_project)
|
|
|
|
|
|
|
|
expect(project.default_issues_tracker?).to be_falsey
|
2013-02-11 06:41:12 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#external_issue_tracker' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-06-03 04:21:18 -04:00
|
|
|
let(:ext_project) { create(:redmine_project) }
|
|
|
|
|
|
|
|
context 'on existing projects with no value for has_external_issue_tracker' do
|
2017-08-10 18:31:42 -04:00
|
|
|
before do
|
2016-06-03 04:21:18 -04:00
|
|
|
project.update_column(:has_external_issue_tracker, nil)
|
|
|
|
ext_project.update_column(:has_external_issue_tracker, nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates the has_external_issue_tracker boolean' do
|
|
|
|
expect do
|
|
|
|
project.external_issue_tracker
|
|
|
|
end.to change { project.reload.has_external_issue_tracker }.to(false)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
ext_project.external_issue_tracker
|
|
|
|
end.to change { ext_project.reload.has_external_issue_tracker }.to(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil and does not query services when there is no external issue tracker' do
|
|
|
|
expect(project).not_to receive(:services)
|
|
|
|
|
|
|
|
expect(project.external_issue_tracker).to eq(nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'retrieves external_issue_tracker querying services and cache it when there is external issue tracker' do
|
|
|
|
ext_project.reload # Factory returns a project with changed attributes
|
|
|
|
expect(ext_project).to receive(:services).once.and_call_original
|
|
|
|
|
|
|
|
2.times { expect(ext_project.external_issue_tracker).to be_a_kind_of(RedmineService) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#cache_has_external_issue_tracker' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, has_external_issue_tracker: nil) }
|
2016-06-03 04:21:18 -04:00
|
|
|
|
|
|
|
it 'stores true if there is any external_issue_tracker' do
|
|
|
|
services = double(:service, external_issue_trackers: [RedmineService.new])
|
|
|
|
expect(project).to receive(:services).and_return(services)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
project.cache_has_external_issue_tracker
|
|
|
|
end.to change { project.has_external_issue_tracker}.to(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'stores false if there is no external_issue_tracker' do
|
|
|
|
services = double(:service, external_issue_trackers: [])
|
|
|
|
expect(project).to receive(:services).and_return(services)
|
|
|
|
|
|
|
|
expect do
|
|
|
|
project.cache_has_external_issue_tracker
|
|
|
|
end.to change { project.has_external_issue_tracker}.to(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-26 16:20:00 -04:00
|
|
|
describe '#has_wiki?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:no_wiki_project) { create(:project, :wiki_disabled, has_external_wiki: false) }
|
|
|
|
let(:wiki_enabled_project) { create(:project) }
|
|
|
|
let(:external_wiki_project) { create(:project, has_external_wiki: true) }
|
2016-08-26 16:20:00 -04:00
|
|
|
|
|
|
|
it 'returns true if project is wiki enabled or has external wiki' do
|
|
|
|
expect(wiki_enabled_project).to have_wiki
|
|
|
|
expect(external_wiki_project).to have_wiki
|
|
|
|
expect(no_wiki_project).not_to have_wiki
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
describe '#external_wiki' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-07-17 10:32:11 -04:00
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
context 'with an active external wiki' do
|
|
|
|
before do
|
|
|
|
create(:service, project: project, type: 'ExternalWikiService', active: true)
|
|
|
|
project.external_wiki
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
it 'sets :has_external_wiki as true' do
|
|
|
|
expect(project.has_external_wiki).to be(true)
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
|
|
|
|
expect(project.has_external_wiki).to be(true)
|
2016-07-17 10:32:11 -04:00
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
project.services.external_wikis.first.destroy
|
|
|
|
|
|
|
|
expect(project.has_external_wiki).to be(false)
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
end
|
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
context 'with an inactive external wiki' do
|
|
|
|
before do
|
|
|
|
create(:service, project: project, type: 'ExternalWikiService', active: false)
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
it 'sets :has_external_wiki as false' do
|
|
|
|
expect(project.has_external_wiki).to be(false)
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
end
|
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
context 'with no external wiki' do
|
|
|
|
before do
|
|
|
|
project.external_wiki
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
|
2016-07-21 04:36:02 -04:00
|
|
|
it 'sets :has_external_wiki as false' do
|
|
|
|
expect(project.has_external_wiki).to be(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets :has_external_wiki as true if an external wiki service is created later' do
|
|
|
|
expect(project.has_external_wiki).to be(false)
|
|
|
|
|
|
|
|
create(:service, project: project, type: 'ExternalWikiService', active: true)
|
|
|
|
|
|
|
|
expect(project.has_external_wiki).to be(true)
|
|
|
|
end
|
2016-07-17 10:32:11 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-07-17 17:22:40 -04:00
|
|
|
describe '#star_count' do
|
|
|
|
it 'counts stars from multiple users' do
|
2014-06-26 03:49:14 -04:00
|
|
|
user1 = create :user
|
|
|
|
user2 = create :user
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, :public)
|
2014-06-26 03:49:14 -04:00
|
|
|
|
|
|
|
expect(project.star_count).to eq(0)
|
2014-07-14 09:17:59 -04:00
|
|
|
|
2014-06-26 03:49:14 -04:00
|
|
|
user1.toggle_star(project)
|
2014-07-14 09:17:59 -04:00
|
|
|
expect(project.reload.star_count).to eq(1)
|
|
|
|
|
2014-06-26 03:49:14 -04:00
|
|
|
user2.toggle_star(project)
|
2014-07-14 09:17:59 -04:00
|
|
|
project.reload
|
|
|
|
expect(project.reload.star_count).to eq(2)
|
|
|
|
|
2014-06-26 03:49:14 -04:00
|
|
|
user1.toggle_star(project)
|
2014-07-14 09:17:59 -04:00
|
|
|
project.reload
|
|
|
|
expect(project.reload.star_count).to eq(1)
|
|
|
|
|
2014-06-26 03:49:14 -04:00
|
|
|
user2.toggle_star(project)
|
2014-07-14 09:17:59 -04:00
|
|
|
project.reload
|
|
|
|
expect(project.reload.star_count).to eq(0)
|
|
|
|
end
|
|
|
|
|
2014-07-17 17:22:40 -04:00
|
|
|
it 'counts stars on the right project' do
|
2014-07-14 09:17:59 -04:00
|
|
|
user = create :user
|
2017-08-02 15:55:11 -04:00
|
|
|
project1 = create(:project, :public)
|
|
|
|
project2 = create(:project, :public)
|
2014-07-14 09:17:59 -04:00
|
|
|
|
|
|
|
expect(project1.star_count).to eq(0)
|
|
|
|
expect(project2.star_count).to eq(0)
|
|
|
|
|
|
|
|
user.toggle_star(project1)
|
|
|
|
project1.reload
|
|
|
|
project2.reload
|
|
|
|
expect(project1.star_count).to eq(1)
|
|
|
|
expect(project2.star_count).to eq(0)
|
|
|
|
|
|
|
|
user.toggle_star(project1)
|
|
|
|
project1.reload
|
|
|
|
project2.reload
|
|
|
|
expect(project1.star_count).to eq(0)
|
|
|
|
expect(project2.star_count).to eq(0)
|
|
|
|
|
|
|
|
user.toggle_star(project2)
|
|
|
|
project1.reload
|
|
|
|
project2.reload
|
|
|
|
expect(project1.star_count).to eq(0)
|
|
|
|
expect(project2.star_count).to eq(1)
|
|
|
|
|
|
|
|
user.toggle_star(project2)
|
|
|
|
project1.reload
|
|
|
|
project2.reload
|
|
|
|
expect(project1.star_count).to eq(0)
|
|
|
|
expect(project2.star_count).to eq(0)
|
2014-06-26 03:49:14 -04:00
|
|
|
end
|
|
|
|
end
|
2014-01-25 12:15:44 -05:00
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#avatar_type' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2014-01-25 12:15:44 -05:00
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'is true if avatar is image' do
|
2014-01-25 12:15:44 -05:00
|
|
|
project.update_attribute(:avatar, 'uploads/avatar.png')
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.avatar_type).to be_truthy
|
2014-01-25 12:15:44 -05:00
|
|
|
end
|
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'is false if avatar is html page' do
|
2014-01-25 12:15:44 -05:00
|
|
|
project.update_attribute(:avatar, 'uploads/avatar.html')
|
2015-02-12 13:17:35 -05:00
|
|
|
expect(project.avatar_type).to eq(['only images allowed'])
|
2014-01-25 12:15:44 -05:00
|
|
|
end
|
|
|
|
end
|
2015-02-28 12:07:53 -05:00
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#avatar_url' do
|
2015-02-28 12:07:53 -05:00
|
|
|
subject { project.avatar_url }
|
|
|
|
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2015-02-28 12:07:53 -05:00
|
|
|
|
2017-05-01 16:15:16 -04:00
|
|
|
context 'when avatar file is uploaded' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, :with_avatar) }
|
2017-07-17 09:46:59 -04:00
|
|
|
let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" }
|
2017-05-10 00:26:17 -04:00
|
|
|
let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
|
2015-02-28 12:07:53 -05:00
|
|
|
|
2017-05-10 00:26:17 -04:00
|
|
|
it 'shows correct url' do
|
|
|
|
expect(project.avatar_url).to eq(avatar_path)
|
|
|
|
expect(project.avatar_url(only_path: false)).to eq([gitlab_host, avatar_path].join)
|
|
|
|
|
|
|
|
allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)
|
|
|
|
|
|
|
|
expect(project.avatar_url).to eq([gitlab_host, avatar_path].join)
|
|
|
|
end
|
2015-02-28 12:07:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'When avatar file in git' do
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:avatar_in_git) { true }
|
|
|
|
end
|
|
|
|
|
2017-05-03 11:25:31 -04:00
|
|
|
let(:avatar_path) { "/#{project.full_path}/avatar" }
|
2015-02-28 12:07:53 -05:00
|
|
|
|
2017-06-29 12:03:17 -04:00
|
|
|
it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
|
2015-02-28 12:07:53 -05:00
|
|
|
end
|
2016-03-24 12:29:00 -04:00
|
|
|
|
|
|
|
context 'when git repo is empty' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-03-24 12:29:00 -04:00
|
|
|
|
2017-06-29 12:03:17 -04:00
|
|
|
it { is_expected.to eq nil }
|
2016-03-24 12:29:00 -04:00
|
|
|
end
|
2015-02-28 12:07:53 -05:00
|
|
|
end
|
2015-09-23 06:18:16 -04:00
|
|
|
|
2016-08-11 05:55:23 -04:00
|
|
|
describe '#pipeline_for' do
|
2017-01-19 16:16:33 -05:00
|
|
|
let(:project) { create(:project, :repository) }
|
2016-08-11 05:55:23 -04:00
|
|
|
let!(:pipeline) { create_pipeline }
|
2016-04-16 16:43:40 -04:00
|
|
|
|
2016-08-11 05:55:23 -04:00
|
|
|
shared_examples 'giving the correct pipeline' do
|
|
|
|
it { is_expected.to eq(pipeline) }
|
2016-04-16 16:43:40 -04:00
|
|
|
|
2016-08-11 05:55:23 -04:00
|
|
|
context 'return latest' do
|
|
|
|
let!(:pipeline2) { create_pipeline }
|
2016-04-16 16:43:40 -04:00
|
|
|
|
2016-08-11 05:55:23 -04:00
|
|
|
it { is_expected.to eq(pipeline2) }
|
2016-04-16 16:43:40 -04:00
|
|
|
end
|
2016-08-11 05:55:23 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'with explicit sha' do
|
|
|
|
subject { project.pipeline_for('master', pipeline.sha) }
|
|
|
|
|
|
|
|
it_behaves_like 'giving the correct pipeline'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with implicit sha' do
|
|
|
|
subject { project.pipeline_for('master') }
|
|
|
|
|
|
|
|
it_behaves_like 'giving the correct pipeline'
|
|
|
|
end
|
2016-04-16 16:43:40 -04:00
|
|
|
|
2016-08-11 05:55:23 -04:00
|
|
|
def create_pipeline
|
|
|
|
create(:ci_pipeline,
|
|
|
|
project: project,
|
|
|
|
ref: 'master',
|
|
|
|
sha: project.commit('master').sha)
|
2016-04-16 16:43:40 -04:00
|
|
|
end
|
2015-09-23 06:18:16 -04:00
|
|
|
end
|
2015-09-23 07:11:40 -04:00
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#builds_enabled' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2015-09-23 07:11:40 -04:00
|
|
|
|
2015-11-09 10:48:03 -05:00
|
|
|
subject { project.builds_enabled }
|
|
|
|
|
|
|
|
it { expect(project.builds_enabled?).to be_truthy }
|
2015-09-23 07:11:40 -04:00
|
|
|
end
|
2015-10-06 10:35:51 -04:00
|
|
|
|
2017-01-20 06:25:53 -05:00
|
|
|
describe '.with_shared_runners' do
|
2017-07-25 13:09:00 -04:00
|
|
|
subject { described_class.with_shared_runners }
|
2017-01-20 06:25:53 -05:00
|
|
|
|
|
|
|
context 'when shared runners are enabled for project' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project, shared_runners_enabled: true) }
|
2017-01-20 06:25:53 -05:00
|
|
|
|
|
|
|
it "returns a project" do
|
|
|
|
is_expected.to eq([project])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when shared runners are disabled for project' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project, shared_runners_enabled: false) }
|
2017-01-20 06:25:53 -05:00
|
|
|
|
|
|
|
it "returns an empty array" do
|
|
|
|
is_expected.to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-10 23:35:47 -04:00
|
|
|
describe '.cached_count', :use_clean_rails_memory_store_caching do
|
2016-08-09 17:08:33 -04:00
|
|
|
let(:group) { create(:group, :public) }
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project1) { create(:project, :public, group: group) }
|
|
|
|
let!(:project2) { create(:project, :public, group: group) }
|
2016-08-09 17:08:33 -04:00
|
|
|
|
|
|
|
it 'returns total project count' do
|
2017-07-25 13:09:00 -04:00
|
|
|
expect(described_class).to receive(:count).once.and_call_original
|
2016-08-09 17:08:33 -04:00
|
|
|
|
|
|
|
3.times do
|
2017-07-25 13:09:00 -04:00
|
|
|
expect(described_class.cached_count).to eq(2)
|
2016-08-09 17:08:33 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-10-06 10:35:51 -04:00
|
|
|
describe '.trending' do
|
2016-03-18 20:04:53 -04:00
|
|
|
let(:group) { create(:group, :public) }
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project1) { create(:project, :public, group: group) }
|
|
|
|
let(:project2) { create(:project, :public, group: group) }
|
2015-10-06 10:35:51 -04:00
|
|
|
|
|
|
|
before do
|
|
|
|
2.times do
|
|
|
|
create(:note_on_commit, project: project1)
|
|
|
|
end
|
|
|
|
|
|
|
|
create(:note_on_commit, project: project2)
|
|
|
|
|
2016-10-07 09:24:09 -04:00
|
|
|
TrendingProject.refresh!
|
2015-10-06 10:35:51 -04:00
|
|
|
end
|
|
|
|
|
2016-10-07 09:24:09 -04:00
|
|
|
subject { described_class.trending.to_a }
|
2015-10-06 10:35:51 -04:00
|
|
|
|
2016-10-07 09:24:09 -04:00
|
|
|
it 'sorts projects by the amount of notes in descending order' do
|
|
|
|
expect(subject).to eq([project1, project2])
|
2015-10-06 10:35:51 -04:00
|
|
|
end
|
2016-10-04 07:21:26 -04:00
|
|
|
|
|
|
|
it 'does not take system notes into account' do
|
|
|
|
10.times do
|
|
|
|
create(:note_on_commit, project: project2, system: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(described_class.trending.to_a).to eq([project1, project2])
|
|
|
|
end
|
2015-10-06 10:35:51 -04:00
|
|
|
end
|
2015-11-18 06:29:45 -05:00
|
|
|
|
2017-05-23 16:40:07 -04:00
|
|
|
describe '.starred_by' do
|
|
|
|
it 'returns only projects starred by the given user' do
|
|
|
|
user1 = create(:user)
|
|
|
|
user2 = create(:user)
|
2017-08-02 15:55:11 -04:00
|
|
|
project1 = create(:project)
|
|
|
|
project2 = create(:project)
|
|
|
|
create(:project)
|
2017-05-23 16:40:07 -04:00
|
|
|
user1.toggle_star(project1)
|
|
|
|
user2.toggle_star(project2)
|
|
|
|
|
2017-07-25 13:09:00 -04:00
|
|
|
expect(described_class.starred_by(user1)).to contain_exactly(project1)
|
2017-05-23 16:40:07 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-11-18 06:29:45 -05:00
|
|
|
describe '.visible_to_user' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project, :private) }
|
2015-11-18 06:29:45 -05:00
|
|
|
let!(:user) { create(:user) }
|
|
|
|
|
|
|
|
subject { described_class.visible_to_user(user) }
|
|
|
|
|
|
|
|
describe 'when a user has access to a project' do
|
|
|
|
before do
|
2016-09-16 11:54:21 -04:00
|
|
|
project.add_user(user, Gitlab::Access::MASTER)
|
2015-11-18 06:29:45 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to eq([project]) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'when a user does not have access to any projects' do
|
|
|
|
it { is_expected.to eq([]) }
|
|
|
|
end
|
|
|
|
end
|
2015-12-04 06:55:23 -05:00
|
|
|
|
2016-06-29 23:35:00 -04:00
|
|
|
context 'repository storage by default' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-06-29 23:35:00 -04:00
|
|
|
|
|
|
|
before do
|
2016-11-03 10:12:20 -04:00
|
|
|
storages = {
|
2017-02-28 16:08:40 -05:00
|
|
|
'default' => { 'path' => 'tmp/tests/repositories' },
|
2017-05-03 07:22:03 -04:00
|
|
|
'picked' => { 'path' => 'tmp/tests/repositories' }
|
2016-11-03 10:12:20 -04:00
|
|
|
}
|
2016-06-29 23:35:00 -04:00
|
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
|
|
end
|
|
|
|
|
2016-11-03 10:12:20 -04:00
|
|
|
it 'picks storage from ApplicationSetting' do
|
|
|
|
expect_any_instance_of(ApplicationSetting).to receive(:pick_repository_storage).and_return('picked')
|
|
|
|
|
|
|
|
expect(project.repository_storage).to eq('picked')
|
|
|
|
end
|
2016-06-29 23:35:00 -04:00
|
|
|
end
|
|
|
|
|
2015-12-04 06:55:23 -05:00
|
|
|
context 'shared runners by default' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2015-12-04 06:55:23 -05:00
|
|
|
|
|
|
|
subject { project.shared_runners_enabled }
|
|
|
|
|
|
|
|
context 'are enabled' do
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
stub_application_setting(shared_runners_enabled: true)
|
|
|
|
end
|
2015-12-04 06:55:23 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'are disabled' do
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
stub_application_setting(shared_runners_enabled: false)
|
|
|
|
end
|
2015-12-04 06:55:23 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_falsey }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-12 05:53:50 -04:00
|
|
|
describe '#any_runners' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
|
2016-02-17 16:56:33 -05:00
|
|
|
let(:specific_runner) { create(:ci_runner) }
|
|
|
|
let(:shared_runner) { create(:ci_runner, :shared) }
|
2015-12-04 06:55:23 -05:00
|
|
|
|
|
|
|
context 'for shared runners disabled' do
|
|
|
|
let(:shared_runners_enabled) { false }
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'has no runners available' do
|
2015-12-04 06:55:23 -05:00
|
|
|
expect(project.any_runners?).to be_falsey
|
|
|
|
end
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'has a specific runner' do
|
2015-12-10 11:44:06 -05:00
|
|
|
project.runners << specific_runner
|
2015-12-04 06:55:23 -05:00
|
|
|
expect(project.any_runners?).to be_truthy
|
|
|
|
end
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'has a shared runner, but they are prohibited to use' do
|
2015-12-04 06:55:23 -05:00
|
|
|
shared_runner
|
|
|
|
expect(project.any_runners?).to be_falsey
|
|
|
|
end
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2015-12-04 06:55:23 -05:00
|
|
|
it 'checks the presence of specific runner' do
|
2015-12-10 11:44:06 -05:00
|
|
|
project.runners << specific_runner
|
2015-12-04 06:55:23 -05:00
|
|
|
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2015-12-04 06:55:23 -05:00
|
|
|
context 'for shared runners enabled' do
|
|
|
|
let(:shared_runners_enabled) { true }
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2016-08-01 11:00:44 -04:00
|
|
|
it 'has a shared runner' do
|
2015-12-04 06:55:23 -05:00
|
|
|
shared_runner
|
|
|
|
expect(project.any_runners?).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'checks the presence of shared runner' do
|
|
|
|
shared_runner
|
|
|
|
expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-12-24 04:33:26 -05:00
|
|
|
|
2017-01-20 06:25:53 -05:00
|
|
|
describe '#shared_runners' do
|
|
|
|
let!(:runner) { create(:ci_runner, :shared) }
|
|
|
|
|
|
|
|
subject { project.shared_runners }
|
|
|
|
|
|
|
|
context 'when shared runners are enabled for project' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project, shared_runners_enabled: true) }
|
2017-01-20 06:25:53 -05:00
|
|
|
|
|
|
|
it "returns a list of shared runners" do
|
|
|
|
is_expected.to eq([runner])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when shared runners are disabled for project' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project, shared_runners_enabled: false) }
|
2017-01-20 06:25:53 -05:00
|
|
|
|
|
|
|
it "returns a empty list" do
|
|
|
|
is_expected.to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-24 04:33:26 -05:00
|
|
|
describe '#visibility_level_allowed?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, :internal) }
|
2015-12-24 04:33:26 -05:00
|
|
|
|
|
|
|
context 'when checking on non-forked project' do
|
|
|
|
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
|
|
|
|
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
|
|
|
|
it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_truthy }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when checking on forked project' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, :internal) }
|
|
|
|
let(:forked_project) { create(:project, forked_from_project: project) }
|
2015-12-24 04:33:26 -05:00
|
|
|
|
|
|
|
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
|
|
|
|
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
|
|
|
|
it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
|
|
|
|
end
|
2016-03-01 06:02:06 -05:00
|
|
|
end
|
2015-12-24 04:33:26 -05:00
|
|
|
|
2016-02-19 14:12:56 -05:00
|
|
|
describe '#pages_deployed?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create :project }
|
2016-02-19 14:12:56 -05:00
|
|
|
|
|
|
|
subject { project.pages_deployed? }
|
|
|
|
|
|
|
|
context 'if public folder does exist' do
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true)
|
|
|
|
end
|
2016-02-19 14:12:56 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
end
|
|
|
|
|
|
|
|
context "if public folder doesn't exist" do
|
|
|
|
it { is_expected.to be_falsey }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-20 05:34:09 -04:00
|
|
|
describe '#pages_url' do
|
|
|
|
let(:group) { create :group, name: group_name }
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create :project, namespace: group, name: project_name }
|
2017-07-20 05:34:09 -04:00
|
|
|
let(:domain) { 'Example.com' }
|
|
|
|
|
|
|
|
subject { project.pages_url }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(Settings.pages).to receive(:host).and_return(domain)
|
|
|
|
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'group page' do
|
|
|
|
let(:group_name) { 'Group' }
|
|
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
|
|
|
|
it { is_expected.to eq("http://group.example.com") }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'project page' do
|
|
|
|
let(:group_name) { 'Group' }
|
|
|
|
let(:project_name) { 'Project' }
|
|
|
|
|
|
|
|
it { is_expected.to eq("http://group.example.com/project") }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-01 06:02:06 -05:00
|
|
|
describe '.search' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, description: 'kitten mittens') }
|
2016-03-08 19:01:33 -05:00
|
|
|
|
2016-03-01 06:02:06 -05:00
|
|
|
it 'returns projects with a matching name' do
|
|
|
|
expect(described_class.search(project.name)).to eq([project])
|
|
|
|
end
|
2016-03-08 19:01:33 -05:00
|
|
|
|
2016-03-01 06:02:06 -05:00
|
|
|
it 'returns projects with a partially matching name' do
|
|
|
|
expect(described_class.search(project.name[0..2])).to eq([project])
|
|
|
|
end
|
2016-03-08 19:01:33 -05:00
|
|
|
|
2016-03-01 06:02:06 -05:00
|
|
|
it 'returns projects with a matching name regardless of the casing' do
|
|
|
|
expect(described_class.search(project.name.upcase)).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching description' do
|
|
|
|
expect(described_class.search(project.description)).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a partially matching description' do
|
|
|
|
expect(described_class.search('kitten')).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching description regardless of the casing' do
|
|
|
|
expect(described_class.search('KITTEN')).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching path' do
|
|
|
|
expect(described_class.search(project.path)).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a partially matching path' do
|
|
|
|
expect(described_class.search(project.path[0..2])).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching path regardless of the casing' do
|
|
|
|
expect(described_class.search(project.path.upcase)).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching namespace name' do
|
|
|
|
expect(described_class.search(project.namespace.name)).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a partially matching namespace name' do
|
|
|
|
expect(described_class.search(project.namespace.name[0..2])).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching namespace name regardless of the casing' do
|
|
|
|
expect(described_class.search(project.namespace.name.upcase)).to eq([project])
|
|
|
|
end
|
2016-03-15 11:46:17 -04:00
|
|
|
|
|
|
|
it 'returns projects when eager loading namespaces' do
|
|
|
|
relation = described_class.all.includes(:namespace)
|
|
|
|
|
|
|
|
expect(relation.search(project.namespace.name)).to eq([project])
|
2016-03-08 19:01:33 -05:00
|
|
|
end
|
2017-06-29 02:56:48 -04:00
|
|
|
|
|
|
|
describe 'with pending_delete project' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:pending_delete_project) { create(:project, pending_delete: true) }
|
2017-06-29 02:56:48 -04:00
|
|
|
|
|
|
|
it 'shows pending deletion project' do
|
|
|
|
search_result = described_class.search(pending_delete_project.name)
|
|
|
|
|
|
|
|
expect(search_result).to eq([pending_delete_project])
|
|
|
|
end
|
|
|
|
end
|
2015-12-24 04:33:26 -05:00
|
|
|
end
|
2016-02-25 11:43:47 -05:00
|
|
|
|
|
|
|
describe '#expire_caches_before_rename' do
|
2017-01-19 16:16:33 -05:00
|
|
|
let(:project) { create(:project, :repository) }
|
2016-02-25 11:43:47 -05:00
|
|
|
let(:repo) { double(:repo, exists?: true) }
|
|
|
|
let(:wiki) { double(:wiki, exists?: true) }
|
|
|
|
|
|
|
|
it 'expires the caches of the repository and wiki' do
|
2017-06-21 09:48:12 -04:00
|
|
|
allow(Repository).to receive(:new)
|
|
|
|
.with('foo', project)
|
|
|
|
.and_return(repo)
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2017-06-21 09:48:12 -04:00
|
|
|
allow(Repository).to receive(:new)
|
|
|
|
.with('foo.wiki', project)
|
|
|
|
.and_return(wiki)
|
2016-02-25 11:43:47 -05:00
|
|
|
|
2016-04-22 03:39:31 -04:00
|
|
|
expect(repo).to receive(:before_delete)
|
|
|
|
expect(wiki).to receive(:before_delete)
|
2016-02-25 11:43:47 -05:00
|
|
|
|
|
|
|
project.expire_caches_before_rename('foo')
|
|
|
|
end
|
|
|
|
end
|
2016-03-01 06:16:15 -05:00
|
|
|
|
|
|
|
describe '.search_by_title' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, name: 'kittens') }
|
2016-03-01 06:16:15 -05:00
|
|
|
|
|
|
|
it 'returns projects with a matching name' do
|
|
|
|
expect(described_class.search_by_title(project.name)).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a partially matching name' do
|
|
|
|
expect(described_class.search_by_title('kitten')).to eq([project])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns projects with a matching name regardless of the casing' do
|
|
|
|
expect(described_class.search_by_title('KITTENS')).to eq([project])
|
|
|
|
end
|
|
|
|
end
|
2016-03-16 19:16:42 -04:00
|
|
|
|
|
|
|
context 'when checking projects from groups' do
|
|
|
|
let(:private_group) { create(:group, visibility_level: 0) }
|
|
|
|
let(:internal_group) { create(:group, visibility_level: 10) }
|
|
|
|
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:private_project) { create :project, :private, group: private_group }
|
|
|
|
let(:internal_project) { create :project, :internal, group: internal_group }
|
2016-03-16 19:16:42 -04:00
|
|
|
|
|
|
|
context 'when group is private project can not be internal' do
|
|
|
|
it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when group is internal project can not be public' do
|
|
|
|
it { expect(internal_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
|
|
|
|
end
|
|
|
|
end
|
2016-03-21 18:22:21 -04:00
|
|
|
|
2016-03-18 10:31:19 -04:00
|
|
|
describe '#create_repository' do
|
2017-01-19 16:16:33 -05:00
|
|
|
let(:project) { create(:project, :repository) }
|
2016-03-18 10:31:19 -04:00
|
|
|
let(:shell) { Gitlab::Shell.new }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:gitlab_shell).and_return(shell)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'using a regular repository' do
|
|
|
|
it 'creates the repository' do
|
2017-06-21 09:48:12 -04:00
|
|
|
expect(shell).to receive(:add_repository)
|
2017-09-28 13:07:22 -04:00
|
|
|
.with(project.repository_storage, project.disk_path)
|
2017-06-21 09:48:12 -04:00
|
|
|
.and_return(true)
|
2016-03-18 10:31:19 -04:00
|
|
|
|
|
|
|
expect(project.repository).to receive(:after_create)
|
|
|
|
|
|
|
|
expect(project.create_repository).to eq(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'adds an error if the repository could not be created' do
|
2017-06-21 09:48:12 -04:00
|
|
|
expect(shell).to receive(:add_repository)
|
2017-09-28 13:07:22 -04:00
|
|
|
.with(project.repository_storage, project.disk_path)
|
2017-06-21 09:48:12 -04:00
|
|
|
.and_return(false)
|
2016-03-18 10:31:19 -04:00
|
|
|
|
|
|
|
expect(project.repository).not_to receive(:after_create)
|
|
|
|
|
|
|
|
expect(project.create_repository).to eq(false)
|
|
|
|
expect(project.errors).not_to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'using a forked repository' do
|
|
|
|
it 'does nothing' do
|
|
|
|
expect(project).to receive(:forked?).and_return(true)
|
|
|
|
expect(shell).not_to receive(:add_repository)
|
|
|
|
|
|
|
|
project.create_repository
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-04-29 16:21:06 -04:00
|
|
|
|
2017-06-28 09:42:51 -04:00
|
|
|
describe '#ensure_repository' do
|
|
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
let(:shell) { Gitlab::Shell.new }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:gitlab_shell).and_return(shell)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates the repository if it not exist' do
|
|
|
|
allow(project).to receive(:repository_exists?)
|
|
|
|
.and_return(false)
|
|
|
|
|
|
|
|
allow(shell).to receive(:add_repository)
|
2017-07-21 00:13:26 -04:00
|
|
|
.with(project.repository_storage_path, project.disk_path)
|
2017-06-28 09:42:51 -04:00
|
|
|
.and_return(true)
|
|
|
|
|
2017-07-03 13:37:20 -04:00
|
|
|
expect(project).to receive(:create_repository).with(force: true)
|
2017-06-28 09:42:51 -04:00
|
|
|
|
|
|
|
project.ensure_repository
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not create the repository if it exists' do
|
|
|
|
allow(project).to receive(:repository_exists?)
|
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
expect(project).not_to receive(:create_repository)
|
|
|
|
|
|
|
|
project.ensure_repository
|
|
|
|
end
|
2017-06-30 02:26:23 -04:00
|
|
|
|
|
|
|
it 'creates the repository if it is a fork' do
|
|
|
|
expect(project).to receive(:forked?).and_return(true)
|
|
|
|
|
|
|
|
allow(project).to receive(:repository_exists?)
|
|
|
|
.and_return(false)
|
|
|
|
|
|
|
|
expect(shell).to receive(:add_repository)
|
2017-09-28 13:07:22 -04:00
|
|
|
.with(project.repository_storage, project.disk_path)
|
2017-06-30 02:26:23 -04:00
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
project.ensure_repository
|
|
|
|
end
|
2017-06-28 09:42:51 -04:00
|
|
|
end
|
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
describe '#user_can_push_to_empty_repo?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-08-01 11:48:15 -04:00
|
|
|
let(:user) { create(:user) }
|
2016-04-29 16:21:06 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
it 'returns false when default_branch_protection is in full protection and user is developer' do
|
|
|
|
project.team << [user, :developer]
|
|
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_FULL)
|
2016-08-01 05:06:57 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
|
2016-04-29 16:21:06 -04:00
|
|
|
end
|
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
it 'returns false when default_branch_protection only lets devs merge and user is dev' do
|
|
|
|
project.team << [user, :developer]
|
|
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
|
2016-08-01 05:06:57 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
expect(project.user_can_push_to_empty_repo?(user)).to be_falsey
|
2016-06-15 01:45:01 -04:00
|
|
|
end
|
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
it 'returns true when default_branch_protection lets devs push and user is developer' do
|
|
|
|
project.team << [user, :developer]
|
|
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
|
2016-08-01 05:06:57 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
|
|
|
|
end
|
2016-08-01 05:06:57 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
it 'returns true when default_branch_protection is unprotected and user is developer' do
|
|
|
|
project.team << [user, :developer]
|
|
|
|
stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
2016-08-01 05:06:57 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
|
2016-04-29 16:21:06 -04:00
|
|
|
end
|
2016-06-15 01:45:01 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
it 'returns true when user is master' do
|
|
|
|
project.team << [user, :master]
|
2016-06-15 01:45:01 -04:00
|
|
|
|
2016-08-01 11:48:15 -04:00
|
|
|
expect(project.user_can_push_to_empty_repo?(user)).to be_truthy
|
2016-06-15 01:45:01 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-15 22:24:05 -05:00
|
|
|
describe '#container_registry_url' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-05-16 18:37:28 -04:00
|
|
|
|
2016-12-15 22:24:05 -05:00
|
|
|
subject { project.container_registry_url }
|
2016-05-16 18:37:28 -04:00
|
|
|
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
stub_container_registry_config(**registry_settings)
|
|
|
|
end
|
2016-05-16 18:37:28 -04:00
|
|
|
|
|
|
|
context 'for enabled registry' do
|
|
|
|
let(:registry_settings) do
|
2017-03-24 08:27:05 -04:00
|
|
|
{ enabled: true,
|
|
|
|
host_port: 'example.com' }
|
2016-05-16 18:37:28 -04:00
|
|
|
end
|
|
|
|
|
2016-05-23 19:37:59 -04:00
|
|
|
it { is_expected.not_to be_nil }
|
2016-05-16 18:37:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'for disabled registry' do
|
|
|
|
let(:registry_settings) do
|
2017-03-24 08:27:05 -04:00
|
|
|
{ enabled: false }
|
2016-05-16 18:37:28 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it { is_expected.to be_nil }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-04-03 10:29:11 -04:00
|
|
|
describe '#has_container_registry_tags?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-04-03 10:29:11 -04:00
|
|
|
|
|
|
|
context 'when container registry is enabled' do
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
stub_container_registry_config(enabled: true)
|
|
|
|
end
|
2017-04-03 10:29:11 -04:00
|
|
|
|
|
|
|
context 'when tags are present for multi-level registries' do
|
|
|
|
before do
|
|
|
|
create(:container_repository, project: project, name: 'image')
|
|
|
|
|
|
|
|
stub_container_registry_tags(repository: /image/,
|
|
|
|
tags: %w[latest rc1])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should have image tags' do
|
|
|
|
expect(project).to have_container_registry_tags
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when tags are present for root repository' do
|
|
|
|
before do
|
|
|
|
stub_container_registry_tags(repository: project.full_path,
|
|
|
|
tags: %w[latest rc1 pre1])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should have image tags' do
|
|
|
|
expect(project).to have_container_registry_tags
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there are no tags at all' do
|
|
|
|
before do
|
|
|
|
stub_container_registry_tags(repository: :any, tags: [])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should not have image tags' do
|
|
|
|
expect(project).not_to have_container_registry_tags
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when container registry is disabled' do
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
stub_container_registry_config(enabled: false)
|
|
|
|
end
|
2017-04-03 10:29:11 -04:00
|
|
|
|
|
|
|
it 'should not have image tags' do
|
|
|
|
expect(project).not_to have_container_registry_tags
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should not check root repository tags' do
|
|
|
|
expect(project).not_to receive(:full_path)
|
|
|
|
expect(project).not_to have_container_registry_tags
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'should iterate through container repositories' do
|
|
|
|
expect(project).to receive(:container_repositories)
|
|
|
|
expect(project).not_to have_container_registry_tags
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-05 08:11:01 -04:00
|
|
|
describe '#ci_config_path=' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-06-30 03:23:46 -04:00
|
|
|
|
|
|
|
it 'sets nil' do
|
2017-07-05 08:11:01 -04:00
|
|
|
project.update!(ci_config_path: nil)
|
2017-06-30 03:23:46 -04:00
|
|
|
|
2017-07-05 08:11:01 -04:00
|
|
|
expect(project.ci_config_path).to be_nil
|
2017-06-30 03:23:46 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'sets a string' do
|
2017-07-05 08:11:01 -04:00
|
|
|
project.update!(ci_config_path: 'foo/.gitlab_ci.yml')
|
2017-06-30 03:23:46 -04:00
|
|
|
|
2017-07-05 08:11:01 -04:00
|
|
|
expect(project.ci_config_path).to eq('foo/.gitlab_ci.yml')
|
2017-06-30 03:23:46 -04:00
|
|
|
end
|
|
|
|
|
2017-07-05 15:23:10 -04:00
|
|
|
it 'sets a string but removes all leading slashes and null characters' do
|
2017-07-05 08:11:01 -04:00
|
|
|
project.update!(ci_config_path: "///f\0oo/\0/.gitlab_ci.yml")
|
2017-06-30 03:23:46 -04:00
|
|
|
|
2017-07-05 08:11:01 -04:00
|
|
|
expect(project.ci_config_path).to eq('foo//.gitlab_ci.yml')
|
2017-06-30 03:23:46 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-10-25 15:43:24 -04:00
|
|
|
describe 'Project import job' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project, import_url: generate(:url)) }
|
2016-10-25 15:43:24 -04:00
|
|
|
|
|
|
|
before do
|
2017-05-26 18:55:06 -04:00
|
|
|
allow_any_instance_of(Gitlab::Shell).to receive(:import_repository)
|
2017-07-21 00:13:26 -04:00
|
|
|
.with(project.repository_storage_path, project.disk_path, project.import_url)
|
2017-05-26 18:55:06 -04:00
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
expect_any_instance_of(Repository).to receive(:after_import)
|
|
|
|
.and_call_original
|
2016-10-25 15:43:24 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'imports a project' do
|
|
|
|
expect_any_instance_of(RepositoryImportWorker).to receive(:perform).and_call_original
|
|
|
|
|
2017-08-17 05:00:31 -04:00
|
|
|
expect { project.import_schedule }.to change { project.import_jid }
|
2016-10-25 15:43:24 -04:00
|
|
|
expect(project.reload.import_status).to eq('finished')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-06-28 14:58:26 -04:00
|
|
|
describe 'project import state transitions' do
|
|
|
|
context 'state transition: [:started] => [:finished]' do
|
2017-08-28 06:51:23 -04:00
|
|
|
let(:after_import_service) { spy(:after_import_service) }
|
2017-08-23 09:51:21 -04:00
|
|
|
let(:housekeeping_service) { spy(:housekeeping_service) }
|
2017-06-28 14:58:26 -04:00
|
|
|
|
|
|
|
before do
|
2017-08-28 06:51:23 -04:00
|
|
|
allow(Projects::AfterImportService)
|
|
|
|
.to receive(:new) { after_import_service }
|
2017-08-23 09:51:21 -04:00
|
|
|
|
2017-08-28 06:51:23 -04:00
|
|
|
allow(after_import_service)
|
2017-08-23 09:51:21 -04:00
|
|
|
.to receive(:execute) { housekeeping_service.execute }
|
|
|
|
|
|
|
|
allow(Projects::HousekeepingService)
|
|
|
|
.to receive(:new) { housekeeping_service }
|
2017-06-28 14:58:26 -04:00
|
|
|
end
|
|
|
|
|
2017-08-17 05:00:31 -04:00
|
|
|
it 'resets project import_error' do
|
|
|
|
error_message = 'Some error'
|
|
|
|
mirror = create(:project_empty_repo, :import_started, import_error: error_message)
|
|
|
|
|
|
|
|
expect { mirror.import_finish }.to change { mirror.import_error }.from(error_message).to(nil)
|
|
|
|
end
|
|
|
|
|
2017-06-28 14:58:26 -04:00
|
|
|
it 'performs housekeeping when an import of a fresh project is completed' do
|
|
|
|
project = create(:project_empty_repo, :import_started, import_type: :github)
|
|
|
|
|
|
|
|
project.import_finish
|
|
|
|
|
2017-08-28 06:51:23 -04:00
|
|
|
expect(after_import_service).to have_received(:execute)
|
2017-06-28 14:58:26 -04:00
|
|
|
expect(housekeeping_service).to have_received(:execute)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not perform housekeeping when project repository does not exist' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, :import_started, import_type: :github)
|
2017-06-28 14:58:26 -04:00
|
|
|
|
|
|
|
project.import_finish
|
|
|
|
|
|
|
|
expect(housekeeping_service).not_to have_received(:execute)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not perform housekeeping when project does not have a valid import type' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, :import_started, import_type: nil)
|
2017-06-28 14:58:26 -04:00
|
|
|
|
|
|
|
project.import_finish
|
|
|
|
|
|
|
|
expect(housekeeping_service).not_to have_received(:execute)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-19 13:34:54 -04:00
|
|
|
describe '#latest_successful_builds_for' do
|
2016-07-20 11:17:28 -04:00
|
|
|
def create_pipeline(status = 'success')
|
2016-07-19 13:34:54 -04:00
|
|
|
create(:ci_pipeline, project: project,
|
2016-07-20 10:31:40 -04:00
|
|
|
sha: project.commit.sha,
|
2016-07-19 13:34:54 -04:00
|
|
|
ref: project.default_branch,
|
2016-07-20 11:17:28 -04:00
|
|
|
status: status)
|
2016-07-19 13:34:54 -04:00
|
|
|
end
|
|
|
|
|
2016-07-20 10:31:40 -04:00
|
|
|
def create_build(new_pipeline = pipeline, name = 'test')
|
|
|
|
create(:ci_build, :success, :artifacts,
|
|
|
|
pipeline: new_pipeline,
|
2016-07-20 11:17:28 -04:00
|
|
|
status: new_pipeline.status,
|
2016-07-20 10:31:40 -04:00
|
|
|
name: name)
|
2016-07-19 13:34:54 -04:00
|
|
|
end
|
|
|
|
|
2017-01-19 16:16:33 -05:00
|
|
|
let(:project) { create(:project, :repository) }
|
2016-07-20 10:31:40 -04:00
|
|
|
let(:pipeline) { create_pipeline }
|
2016-07-20 11:17:28 -04:00
|
|
|
|
|
|
|
context 'with many builds' do
|
2016-07-20 12:13:02 -04:00
|
|
|
it 'gives the latest builds from latest pipeline' do
|
2016-07-20 11:56:54 -04:00
|
|
|
pipeline1 = create_pipeline
|
|
|
|
pipeline2 = create_pipeline
|
2016-07-20 12:13:02 -04:00
|
|
|
build1_p2 = create_build(pipeline2, 'test')
|
2016-07-20 11:56:54 -04:00
|
|
|
create_build(pipeline1, 'test')
|
|
|
|
create_build(pipeline1, 'test2')
|
2016-07-20 12:13:02 -04:00
|
|
|
build2_p2 = create_build(pipeline2, 'test2')
|
2016-07-20 11:17:28 -04:00
|
|
|
|
|
|
|
latest_builds = project.latest_successful_builds_for
|
|
|
|
|
2016-07-20 12:13:02 -04:00
|
|
|
expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
|
2016-07-20 11:17:28 -04:00
|
|
|
end
|
|
|
|
end
|
2016-07-20 10:31:40 -04:00
|
|
|
|
2016-07-20 08:05:51 -04:00
|
|
|
context 'with succeeded pipeline' do
|
2016-07-20 11:17:28 -04:00
|
|
|
let!(:build) { create_build }
|
2016-07-20 07:13:32 -04:00
|
|
|
|
2016-07-20 11:17:28 -04:00
|
|
|
context 'standalone pipeline' do
|
2016-07-20 07:13:32 -04:00
|
|
|
it 'returns builds for ref for default_branch' do
|
|
|
|
builds = project.latest_successful_builds_for
|
|
|
|
|
|
|
|
expect(builds).to contain_exactly(build)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns empty relation if the build cannot be found' do
|
|
|
|
builds = project.latest_successful_builds_for('TAIL')
|
2016-07-19 13:34:54 -04:00
|
|
|
|
2016-07-20 07:13:32 -04:00
|
|
|
expect(builds).to be_kind_of(ActiveRecord::Relation)
|
|
|
|
expect(builds).to be_empty
|
|
|
|
end
|
2016-07-19 13:34:54 -04:00
|
|
|
end
|
|
|
|
|
2016-07-20 11:17:28 -04:00
|
|
|
context 'with some pending pipeline' do
|
2016-07-20 09:46:46 -04:00
|
|
|
before do
|
2016-07-20 11:17:28 -04:00
|
|
|
create_build(create_pipeline('pending'))
|
2016-07-20 09:46:46 -04:00
|
|
|
end
|
|
|
|
|
2016-07-20 11:17:28 -04:00
|
|
|
it 'gives the latest build from latest pipeline' do
|
|
|
|
latest_build = project.latest_successful_builds_for
|
2016-07-20 07:13:32 -04:00
|
|
|
|
2016-07-20 11:17:28 -04:00
|
|
|
expect(latest_build).to contain_exactly(build)
|
2016-07-20 07:13:32 -04:00
|
|
|
end
|
2016-07-19 13:34:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with pending pipeline' do
|
|
|
|
before do
|
|
|
|
pipeline.update(status: 'pending')
|
2016-07-20 11:38:47 -04:00
|
|
|
create_build(pipeline)
|
2016-07-19 13:34:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns empty relation' do
|
|
|
|
builds = project.latest_successful_builds_for
|
|
|
|
|
|
|
|
expect(builds).to be_kind_of(ActiveRecord::Relation)
|
|
|
|
expect(builds).to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-07-26 17:22:13 -04:00
|
|
|
describe '#add_import_job' do
|
2017-08-17 05:00:31 -04:00
|
|
|
let(:import_jid) { '123' }
|
|
|
|
|
2016-07-26 17:22:13 -04:00
|
|
|
context 'forked' do
|
2017-06-01 10:27:35 -04:00
|
|
|
let(:forked_project_link) { create(:forked_project_link, :forked_to_empty_project) }
|
2016-07-26 17:22:13 -04:00
|
|
|
let(:forked_from_project) { forked_project_link.forked_from_project }
|
|
|
|
let(:project) { forked_project_link.forked_to_project }
|
|
|
|
|
|
|
|
it 'schedules a RepositoryForkWorker job' do
|
2017-08-17 05:00:31 -04:00
|
|
|
expect(RepositoryForkWorker).to receive(:perform_async).with(
|
|
|
|
project.id,
|
|
|
|
forked_from_project.repository_storage_path,
|
|
|
|
forked_from_project.disk_path,
|
|
|
|
project.namespace.full_path).and_return(import_jid)
|
2016-07-26 17:22:13 -04:00
|
|
|
|
2017-08-17 05:00:31 -04:00
|
|
|
expect(project.add_import_job).to eq(import_jid)
|
2016-07-26 17:22:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'not forked' do
|
|
|
|
it 'schedules a RepositoryImportWorker job' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, import_url: generate(:url))
|
2017-06-01 10:27:35 -04:00
|
|
|
|
2017-08-17 05:00:31 -04:00
|
|
|
expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
|
|
|
|
expect(project.add_import_job).to eq(import_jid)
|
2016-07-26 17:22:13 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-16 11:44:22 -05:00
|
|
|
describe '#gitlab_project_import?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
subject(:project) { build(:project, import_type: 'gitlab_project') }
|
2016-12-16 11:44:22 -05:00
|
|
|
|
|
|
|
it { expect(project.gitlab_project_import?).to be true }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#gitea_import?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
subject(:project) { build(:project, import_type: 'gitea') }
|
2016-12-16 11:44:22 -05:00
|
|
|
|
|
|
|
it { expect(project.gitea_import?).to be true }
|
|
|
|
end
|
|
|
|
|
2016-09-01 19:49:48 -04:00
|
|
|
describe '#lfs_enabled?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-09-01 19:49:48 -04:00
|
|
|
|
|
|
|
shared_examples 'project overrides group' do
|
|
|
|
it 'returns true when enabled in project' do
|
|
|
|
project.update_attribute(:lfs_enabled, true)
|
|
|
|
|
|
|
|
expect(project.lfs_enabled?).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false when disabled in project' do
|
|
|
|
project.update_attribute(:lfs_enabled, false)
|
|
|
|
|
|
|
|
expect(project.lfs_enabled?).to be_falsey
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns the value from the namespace, when no value is set in project' do
|
|
|
|
expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'LFS disabled in group' do
|
|
|
|
before do
|
|
|
|
project.namespace.update_attribute(:lfs_enabled, false)
|
|
|
|
enable_lfs
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'project overrides group'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'LFS enabled in group' do
|
|
|
|
before do
|
|
|
|
project.namespace.update_attribute(:lfs_enabled, true)
|
|
|
|
enable_lfs
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'project overrides group'
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'LFS disabled globally' do
|
|
|
|
shared_examples 'it always returns false' do
|
|
|
|
it do
|
|
|
|
expect(project.lfs_enabled?).to be_falsey
|
|
|
|
expect(project.namespace.lfs_enabled?).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when no values are set' do
|
|
|
|
it_behaves_like 'it always returns false'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when all values are set to true' do
|
|
|
|
before do
|
|
|
|
project.namespace.update_attribute(:lfs_enabled, true)
|
|
|
|
project.update_attribute(:lfs_enabled, true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'it always returns false'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-26 23:32:28 -04:00
|
|
|
describe '#change_head' do
|
2017-01-19 16:16:33 -05:00
|
|
|
let(:project) { create(:project, :repository) }
|
2016-08-12 10:29:13 -04:00
|
|
|
|
2017-08-07 21:10:55 -04:00
|
|
|
it 'returns error if branch does not exist' do
|
|
|
|
expect(project.change_head('unexisted-branch')).to be false
|
|
|
|
expect(project.errors.size).to eq(1)
|
|
|
|
end
|
|
|
|
|
2016-04-26 23:32:28 -04:00
|
|
|
it 'calls the before_change_head and after_change_head methods' do
|
2016-08-12 10:29:13 -04:00
|
|
|
expect(project.repository).to receive(:before_change_head)
|
2016-04-26 23:32:28 -04:00
|
|
|
expect(project.repository).to receive(:after_change_head)
|
|
|
|
|
2016-08-12 10:29:13 -04:00
|
|
|
project.change_head(project.default_branch)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'creates the new reference with rugged' do
|
|
|
|
expect(project.repository.rugged.references).to receive(:create).with('HEAD',
|
|
|
|
"refs/heads/#{project.default_branch}",
|
|
|
|
force: true)
|
|
|
|
project.change_head(project.default_branch)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'copies the gitattributes' do
|
|
|
|
expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
|
|
|
|
project.change_head(project.default_branch)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'reloads the default branch' do
|
|
|
|
expect(project).to receive(:reload_default_branch)
|
|
|
|
project.change_head(project.default_branch)
|
|
|
|
end
|
|
|
|
end
|
2016-09-13 12:28:45 -04:00
|
|
|
|
|
|
|
describe '#pushes_since_gc' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-09-13 12:28:45 -04:00
|
|
|
|
|
|
|
after do
|
|
|
|
project.reset_pushes_since_gc
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without any pushes' do
|
|
|
|
it 'returns 0' do
|
|
|
|
expect(project.pushes_since_gc).to eq(0)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a number of pushes' do
|
|
|
|
it 'returns the number of pushes' do
|
|
|
|
3.times { project.increment_pushes_since_gc }
|
|
|
|
|
|
|
|
expect(project.pushes_since_gc).to eq(3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#increment_pushes_since_gc' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-09-13 12:28:45 -04:00
|
|
|
|
|
|
|
after do
|
|
|
|
project.reset_pushes_since_gc
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'increments the number of pushes since the last GC' do
|
|
|
|
3.times { project.increment_pushes_since_gc }
|
|
|
|
|
|
|
|
expect(project.pushes_since_gc).to eq(3)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#reset_pushes_since_gc' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-09-13 12:28:45 -04:00
|
|
|
|
|
|
|
after do
|
|
|
|
project.reset_pushes_since_gc
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'resets the number of pushes since the last GC' do
|
|
|
|
3.times { project.increment_pushes_since_gc }
|
|
|
|
|
|
|
|
project.reset_pushes_since_gc
|
|
|
|
|
|
|
|
expect(project.pushes_since_gc).to eq(0)
|
|
|
|
end
|
|
|
|
end
|
2016-09-01 19:49:48 -04:00
|
|
|
|
2016-12-16 07:24:03 -05:00
|
|
|
describe '#deployment_variables' do
|
|
|
|
context 'when project has no deployment service' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-12-16 07:24:03 -05:00
|
|
|
|
|
|
|
it 'returns an empty array' do
|
|
|
|
expect(project.deployment_variables).to eq []
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when project has a deployment service' do
|
|
|
|
let(:project) { create(:kubernetes_project) }
|
|
|
|
|
|
|
|
it 'returns variables from this service' do
|
|
|
|
expect(project.deployment_variables).to include(
|
|
|
|
{ key: 'KUBE_TOKEN', value: project.kubernetes_service.token, public: false }
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-31 10:45:51 -04:00
|
|
|
describe '#secret_variables_for' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-05-25 06:05:27 -04:00
|
|
|
|
|
|
|
let!(:secret_variable) do
|
|
|
|
create(:ci_variable, value: 'secret', project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
let!(:protected_variable) do
|
|
|
|
create(:ci_variable, :protected, value: 'protected', project: project)
|
|
|
|
end
|
|
|
|
|
2017-07-06 03:45:38 -04:00
|
|
|
subject { project.secret_variables_for(ref: 'ref') }
|
|
|
|
|
|
|
|
before do
|
|
|
|
stub_application_setting(
|
|
|
|
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
|
|
|
end
|
2017-05-26 13:46:57 -04:00
|
|
|
|
|
|
|
shared_examples 'ref is protected' do
|
|
|
|
it 'contains all the variables' do
|
2017-05-31 12:12:42 -04:00
|
|
|
is_expected.to contain_exactly(secret_variable, protected_variable)
|
2017-05-26 13:46:57 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the ref is not protected' do
|
2017-05-25 06:05:27 -04:00
|
|
|
it 'contains only the secret variables' do
|
2017-05-31 12:12:42 -04:00
|
|
|
is_expected.to contain_exactly(secret_variable)
|
2017-05-25 06:05:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-26 13:46:57 -04:00
|
|
|
context 'when the ref is a protected branch' do
|
|
|
|
before do
|
|
|
|
create(:protected_branch, name: 'ref', project: project)
|
2017-05-25 06:05:27 -04:00
|
|
|
end
|
2017-05-26 13:46:57 -04:00
|
|
|
|
|
|
|
it_behaves_like 'ref is protected'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the ref is a protected tag' do
|
|
|
|
before do
|
|
|
|
create(:protected_tag, name: 'ref', project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it_behaves_like 'ref is protected'
|
2017-05-25 06:05:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-31 15:49:10 -04:00
|
|
|
describe '#protected_for?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-05-31 15:49:10 -04:00
|
|
|
|
|
|
|
subject { project.protected_for?('ref') }
|
|
|
|
|
|
|
|
context 'when the ref is not protected' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(
|
|
|
|
default_branch_protection: Gitlab::Access::PROTECTION_NONE)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns false' do
|
|
|
|
is_expected.to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the ref is a protected branch' do
|
|
|
|
before do
|
|
|
|
create(:protected_branch, name: 'ref', project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true' do
|
|
|
|
is_expected.to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the ref is a protected tag' do
|
|
|
|
before do
|
|
|
|
create(:protected_tag, name: 'ref', project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns true' do
|
|
|
|
is_expected.to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-22 11:58:10 -05:00
|
|
|
describe '#update_project_statistics' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2016-11-22 11:58:10 -05:00
|
|
|
|
|
|
|
it "is called after creation" do
|
|
|
|
expect(project.statistics).to be_a ProjectStatistics
|
|
|
|
expect(project.statistics).to be_persisted
|
|
|
|
end
|
|
|
|
|
|
|
|
it "copies the namespace_id" do
|
|
|
|
expect(project.statistics.namespace_id).to eq project.namespace_id
|
|
|
|
end
|
|
|
|
|
|
|
|
it "updates the namespace_id when changed" do
|
|
|
|
namespace = create(:namespace)
|
|
|
|
project.update(namespace: namespace)
|
|
|
|
|
|
|
|
expect(project.statistics.namespace_id).to eq namespace.id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-01-30 04:35:28 -05:00
|
|
|
describe 'inside_path' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project1) { create(:project, namespace: create(:namespace, path: 'name_pace')) }
|
|
|
|
let!(:project2) { create(:project) }
|
|
|
|
let!(:project3) { create(:project, namespace: create(:namespace, path: 'namespace')) }
|
2017-02-23 18:55:01 -05:00
|
|
|
let!(:path) { project1.namespace.full_path }
|
2017-01-30 04:35:28 -05:00
|
|
|
|
2017-03-21 12:04:12 -04:00
|
|
|
it 'returns correct project' do
|
2017-07-25 13:09:00 -04:00
|
|
|
expect(described_class.inside_path(path)).to eq([project1])
|
2017-03-21 12:04:12 -04:00
|
|
|
end
|
2017-01-30 04:35:28 -05:00
|
|
|
end
|
|
|
|
|
2017-01-29 23:01:31 -05:00
|
|
|
describe '#route_map_for' do
|
2017-08-01 14:51:52 -04:00
|
|
|
let(:project) { create(:project, :repository) }
|
2017-01-29 23:01:31 -05:00
|
|
|
let(:route_map) do
|
|
|
|
<<-MAP.strip_heredoc
|
|
|
|
- source: /source/(.*)/
|
|
|
|
public: '\\1'
|
|
|
|
MAP
|
|
|
|
end
|
|
|
|
|
|
|
|
before do
|
2017-02-15 18:28:29 -05:00
|
|
|
project.repository.create_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master')
|
2017-01-29 23:01:31 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there is a .gitlab/route-map.yml at the commit' do
|
|
|
|
context 'when the route map is valid' do
|
|
|
|
it 'returns a route map' do
|
|
|
|
map = project.route_map_for(project.commit.sha)
|
|
|
|
expect(map).to be_a_kind_of(Gitlab::RouteMap)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the route map is invalid' do
|
|
|
|
let(:route_map) { 'INVALID' }
|
|
|
|
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.route_map_for(project.commit.sha)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there is no .gitlab/route-map.yml at the commit' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.route_map_for(project.commit.parent.sha)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#public_path_for_source_path' do
|
2017-08-01 14:51:52 -04:00
|
|
|
let(:project) { create(:project, :repository) }
|
2017-01-29 23:01:31 -05:00
|
|
|
let(:route_map) do
|
|
|
|
Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
|
|
|
|
- source: /source/(.*)/
|
|
|
|
public: '\\1'
|
|
|
|
MAP
|
|
|
|
end
|
|
|
|
let(:sha) { project.commit.id }
|
|
|
|
|
|
|
|
context 'when there is a route map' do
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:route_map_for).with(sha).and_return(route_map)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the source path is mapped' do
|
|
|
|
it 'returns the public path' do
|
|
|
|
expect(project.public_path_for_source_path('source/file.html', sha)).to eq('file.html')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the source path is not mapped' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.public_path_for_source_path('file.html', sha)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there is no route map' do
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:route_map_for).with(sha).and_return(nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.public_path_for_source_path('source/file.html', sha)).to be_nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-02-04 13:26:11 -05:00
|
|
|
describe '#parent' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-02-04 13:26:11 -05:00
|
|
|
|
|
|
|
it { expect(project.parent).to eq(project.namespace) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#parent_changed?' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-02-04 13:26:11 -05:00
|
|
|
|
2017-06-14 14:18:56 -04:00
|
|
|
before do
|
|
|
|
project.namespace_id = 7
|
|
|
|
end
|
2017-02-04 13:26:11 -05:00
|
|
|
|
|
|
|
it { expect(project.parent_changed?).to be_truthy }
|
|
|
|
end
|
|
|
|
|
2016-09-01 19:49:48 -04:00
|
|
|
def enable_lfs
|
|
|
|
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
|
|
|
|
end
|
2016-02-19 09:11:03 -05:00
|
|
|
|
2016-02-19 14:07:27 -05:00
|
|
|
describe '#pages_url' do
|
2017-02-14 09:03:24 -05:00
|
|
|
let(:group) { create :group, name: 'Group' }
|
|
|
|
let(:nested_group) { create :group, parent: group }
|
2016-02-19 09:11:03 -05:00
|
|
|
let(:domain) { 'Example.com' }
|
|
|
|
|
|
|
|
subject { project.pages_url }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(Settings.pages).to receive(:host).and_return(domain)
|
|
|
|
allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
|
|
|
|
end
|
|
|
|
|
2017-02-14 09:03:24 -05:00
|
|
|
context 'top-level group' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create :project, namespace: group, name: project_name }
|
2016-02-19 09:11:03 -05:00
|
|
|
|
2017-02-14 09:03:24 -05:00
|
|
|
context 'group page' do
|
|
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
|
|
|
|
it { is_expected.to eq("http://group.example.com") }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'project page' do
|
|
|
|
let(:project_name) { 'Project' }
|
|
|
|
|
|
|
|
it { is_expected.to eq("http://group.example.com/project") }
|
|
|
|
end
|
2016-02-19 09:11:03 -05:00
|
|
|
end
|
|
|
|
|
2017-02-14 09:03:24 -05:00
|
|
|
context 'nested group' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create :project, namespace: nested_group, name: project_name }
|
2017-02-14 09:03:24 -05:00
|
|
|
let(:expected_url) { "http://group.example.com/#{nested_group.path}/#{project.path}" }
|
2016-02-19 09:11:03 -05:00
|
|
|
|
2017-02-14 09:03:24 -05:00
|
|
|
context 'group page' do
|
|
|
|
let(:project_name) { 'group.example.com' }
|
|
|
|
|
|
|
|
it { is_expected.to eq(expected_url) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'project page' do
|
|
|
|
let(:project_name) { 'Project' }
|
|
|
|
|
|
|
|
it { is_expected.to eq(expected_url) }
|
|
|
|
end
|
2016-02-19 09:11:03 -05:00
|
|
|
end
|
|
|
|
end
|
2017-02-17 12:18:06 -05:00
|
|
|
|
|
|
|
describe '#http_url_to_repo' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create :project }
|
2017-02-17 12:18:06 -05:00
|
|
|
|
2017-05-30 08:11:58 -04:00
|
|
|
it 'returns the url to the repo without a username' do
|
|
|
|
expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
|
|
|
|
expect(project.http_url_to_repo).not_to include('@')
|
2017-02-17 12:18:06 -05:00
|
|
|
end
|
|
|
|
end
|
2017-03-14 08:35:46 -04:00
|
|
|
|
|
|
|
describe '#pipeline_status' do
|
2017-08-01 14:51:52 -04:00
|
|
|
let(:project) { create(:project, :repository) }
|
2017-03-14 08:35:46 -04:00
|
|
|
it 'builds a pipeline status' do
|
2017-04-03 03:09:11 -04:00
|
|
|
expect(project.pipeline_status).to be_a(Gitlab::Cache::Ci::ProjectPipelineStatus)
|
2017-03-14 08:35:46 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'hase a loaded pipeline status' do
|
|
|
|
expect(project.pipeline_status).to be_loaded
|
|
|
|
end
|
|
|
|
end
|
2017-05-03 06:12:32 -04:00
|
|
|
|
|
|
|
describe '#append_or_update_attribute' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let(:project) { create(:project) }
|
2017-05-03 06:12:32 -04:00
|
|
|
|
|
|
|
it 'shows full error updating an invalid MR' do
|
|
|
|
error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
|
|
|
|
' Validate fork Source project is not a fork of the target project'
|
|
|
|
|
2017-06-21 09:48:12 -04:00
|
|
|
expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }
|
|
|
|
.to raise_error(ActiveRecord::RecordNotSaved, error_message)
|
2017-05-03 06:12:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates the project succesfully' do
|
|
|
|
merge_request = create(:merge_request, target_project: project, source_project: project)
|
|
|
|
|
2017-06-21 09:48:12 -04:00
|
|
|
expect { project.append_or_update_attribute(:merge_requests, [merge_request]) }
|
|
|
|
.not_to raise_error
|
2017-05-03 06:12:32 -04:00
|
|
|
end
|
|
|
|
end
|
2017-05-03 21:39:41 -04:00
|
|
|
|
|
|
|
describe '#last_repository_updated_at' do
|
|
|
|
it 'sets to created_at upon creation' do
|
2017-08-02 15:55:11 -04:00
|
|
|
project = create(:project, created_at: 2.hours.ago)
|
2017-05-03 21:39:41 -04:00
|
|
|
|
|
|
|
expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i)
|
|
|
|
end
|
|
|
|
end
|
2017-06-15 09:19:25 -04:00
|
|
|
|
|
|
|
describe '.public_or_visible_to_user' do
|
|
|
|
let!(:user) { create(:user) }
|
|
|
|
|
|
|
|
let!(:private_project) do
|
2017-08-02 15:55:11 -04:00
|
|
|
create(:project, :private, creator: user, namespace: user.namespace)
|
2017-06-15 09:19:25 -04:00
|
|
|
end
|
|
|
|
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:public_project) { create(:project, :public) }
|
2017-06-15 09:19:25 -04:00
|
|
|
|
|
|
|
context 'with a user' do
|
|
|
|
let(:projects) do
|
2017-07-25 13:09:00 -04:00
|
|
|
described_class.all.public_or_visible_to_user(user)
|
2017-06-15 09:19:25 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes projects the user has access to' do
|
|
|
|
expect(projects).to include(private_project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes projects the user can see' do
|
|
|
|
expect(projects).to include(public_project)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without a user' do
|
|
|
|
it 'only includes public projects' do
|
2017-07-25 13:09:00 -04:00
|
|
|
projects = described_class.all.public_or_visible_to_user
|
2017-06-15 09:19:25 -04:00
|
|
|
|
|
|
|
expect(projects).to eq([public_project])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-05-30 07:54:59 -04:00
|
|
|
|
2017-08-29 06:15:19 -04:00
|
|
|
describe '#pages_available?' do
|
|
|
|
let(:project) { create(:project, group: group) }
|
|
|
|
|
|
|
|
subject { project.pages_available? }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the project is in a top level namespace' do
|
|
|
|
let(:group) { create(:group) }
|
|
|
|
|
|
|
|
it { is_expected.to be(true) }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the project is in a subgroup' do
|
|
|
|
let(:group) { create(:group, :nested) }
|
|
|
|
|
|
|
|
it { is_expected.to be(false) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-30 07:54:59 -04:00
|
|
|
describe '#remove_private_deploy_keys' do
|
2017-08-02 15:55:11 -04:00
|
|
|
let!(:project) { create(:project) }
|
2017-07-28 12:25:13 -04:00
|
|
|
|
|
|
|
context 'for a private deploy key' do
|
|
|
|
let!(:key) { create(:deploy_key, public: false) }
|
|
|
|
let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) }
|
|
|
|
|
|
|
|
context 'when the key is not linked to another project' do
|
|
|
|
it 'removes the key' do
|
|
|
|
project.remove_private_deploy_keys
|
|
|
|
|
|
|
|
expect(project.deploy_keys).not_to include(key)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when the key is linked to another project' do
|
|
|
|
before do
|
2017-08-02 15:55:11 -04:00
|
|
|
another_project = create(:project)
|
2017-07-28 12:25:13 -04:00
|
|
|
create(:deploy_keys_project, deploy_key: key, project: another_project)
|
|
|
|
end
|
2017-05-30 07:54:59 -04:00
|
|
|
|
2017-07-28 12:25:13 -04:00
|
|
|
it 'does not remove the key' do
|
|
|
|
project.remove_private_deploy_keys
|
2017-05-30 07:54:59 -04:00
|
|
|
|
2017-07-28 12:25:13 -04:00
|
|
|
expect(project.deploy_keys).to include(key)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for a public deploy key' do
|
|
|
|
let!(:key) { create(:deploy_key, public: true) }
|
|
|
|
let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) }
|
2017-05-30 07:54:59 -04:00
|
|
|
|
2017-07-28 12:25:13 -04:00
|
|
|
it 'does not remove the key' do
|
|
|
|
project.remove_private_deploy_keys
|
2017-05-30 07:54:59 -04:00
|
|
|
|
2017-07-28 12:25:13 -04:00
|
|
|
expect(project.deploy_keys).to include(key)
|
|
|
|
end
|
2017-05-30 07:54:59 -04:00
|
|
|
end
|
|
|
|
end
|
2017-08-14 09:22:09 -04:00
|
|
|
|
2017-08-17 06:15:57 -04:00
|
|
|
describe '#remove_pages' do
|
|
|
|
let(:project) { create(:project) }
|
2017-08-17 06:36:58 -04:00
|
|
|
let(:namespace) { project.namespace }
|
2017-08-17 06:15:57 -04:00
|
|
|
let(:pages_path) { project.pages_path }
|
|
|
|
|
|
|
|
around do |example|
|
|
|
|
FileUtils.mkdir_p(pages_path)
|
|
|
|
begin
|
|
|
|
example.run
|
|
|
|
ensure
|
|
|
|
FileUtils.rm_rf(pages_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-17 06:36:58 -04:00
|
|
|
it 'removes the pages directory' do
|
|
|
|
expect_any_instance_of(Projects::UpdatePagesConfigurationService).to receive(:execute)
|
|
|
|
expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return(true)
|
|
|
|
expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, namespace.full_path, anything)
|
|
|
|
|
|
|
|
project.remove_pages
|
|
|
|
end
|
|
|
|
|
2017-08-17 06:15:57 -04:00
|
|
|
it 'is a no-op when there is no namespace' do
|
|
|
|
project.update_column(:namespace_id, nil)
|
|
|
|
|
|
|
|
expect_any_instance_of(Projects::UpdatePagesConfigurationService).not_to receive(:execute)
|
|
|
|
expect_any_instance_of(Gitlab::PagesTransfer).not_to receive(:rename_project)
|
|
|
|
|
|
|
|
project.remove_pages
|
|
|
|
end
|
2017-08-17 06:36:58 -04:00
|
|
|
|
|
|
|
it 'is run when the project is destroyed' do
|
|
|
|
expect(project).to receive(:remove_pages).and_call_original
|
|
|
|
|
|
|
|
project.destroy
|
|
|
|
end
|
2017-08-17 06:15:57 -04:00
|
|
|
end
|
|
|
|
|
2017-08-14 09:22:09 -04:00
|
|
|
describe '#forks_count' do
|
|
|
|
it 'returns the number of forks' do
|
|
|
|
project = build(:project)
|
|
|
|
|
|
|
|
allow(project.forks).to receive(:count).and_return(1)
|
|
|
|
|
|
|
|
expect(project.forks_count).to eq(1)
|
|
|
|
end
|
|
|
|
end
|
2017-08-04 01:30:42 -04:00
|
|
|
|
|
|
|
context 'legacy storage' do
|
|
|
|
let(:project) { create(:project, :repository) }
|
|
|
|
let(:gitlab_shell) { Gitlab::Shell.new }
|
|
|
|
|
2017-08-11 03:51:56 -04:00
|
|
|
before do
|
|
|
|
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
|
|
|
|
end
|
|
|
|
|
2017-08-04 01:30:42 -04:00
|
|
|
describe '#base_dir' do
|
|
|
|
it 'returns base_dir based on namespace only' do
|
|
|
|
expect(project.base_dir).to eq(project.namespace.full_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#disk_path' do
|
|
|
|
it 'returns disk_path based on namespace and project path' do
|
|
|
|
expect(project.disk_path).to eq("#{project.namespace.full_path}/#{project.path}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-15 22:49:54 -04:00
|
|
|
describe '#ensure_storage_path_exists' do
|
2017-08-04 01:30:42 -04:00
|
|
|
it 'delegates to gitlab_shell to ensure namespace is created' do
|
|
|
|
expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, project.base_dir)
|
|
|
|
|
2017-08-15 22:49:54 -04:00
|
|
|
project.ensure_storage_path_exists
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-21 09:28:07 -04:00
|
|
|
describe '#legacy_storage?' do
|
|
|
|
it 'returns true when storage_version is nil' do
|
2017-09-06 01:16:26 -04:00
|
|
|
project = build(:project, storage_version: nil)
|
2017-08-21 09:28:07 -04:00
|
|
|
|
|
|
|
expect(project.legacy_storage?).to be_truthy
|
|
|
|
end
|
2017-09-06 01:16:26 -04:00
|
|
|
|
|
|
|
it 'returns true when the storage_version is 0' do
|
|
|
|
project = build(:project, storage_version: 0)
|
|
|
|
|
|
|
|
expect(project.legacy_storage?).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#hashed_storage?' do
|
|
|
|
it 'returns false' do
|
|
|
|
expect(project.hashed_storage?).to be_falsey
|
|
|
|
end
|
2017-08-21 09:28:07 -04:00
|
|
|
end
|
|
|
|
|
2017-08-04 01:30:42 -04:00
|
|
|
describe '#rename_repo' do
|
|
|
|
before do
|
|
|
|
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
|
|
|
|
# call. This makes testing a bit easier.
|
|
|
|
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
|
|
|
|
allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'renames a repository' do
|
|
|
|
stub_container_registry_config(enabled: false)
|
|
|
|
|
|
|
|
expect(gitlab_shell).to receive(:mv_repository)
|
|
|
|
.ordered
|
|
|
|
.with(project.repository_storage_path, "#{project.namespace.full_path}/foo", "#{project.full_path}")
|
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
expect(gitlab_shell).to receive(:mv_repository)
|
|
|
|
.ordered
|
|
|
|
.with(project.repository_storage_path, "#{project.namespace.full_path}/foo.wiki", "#{project.full_path}.wiki")
|
|
|
|
.and_return(true)
|
|
|
|
|
|
|
|
expect_any_instance_of(SystemHooksService)
|
|
|
|
.to receive(:execute_hooks_for)
|
|
|
|
.with(project, :rename)
|
|
|
|
|
|
|
|
expect_any_instance_of(Gitlab::UploadsTransfer)
|
|
|
|
.to receive(:rename_project)
|
|
|
|
.with('foo', project.path, project.namespace.full_path)
|
|
|
|
|
|
|
|
expect(project).to receive(:expire_caches_before_rename)
|
|
|
|
|
|
|
|
expect(project).to receive(:expires_full_path_cache)
|
|
|
|
|
|
|
|
project.rename_repo
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'container registry with images' do
|
|
|
|
let(:container_repository) { create(:container_repository) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
stub_container_registry_config(enabled: true)
|
|
|
|
stub_container_registry_tags(repository: :any, tags: ['tag'])
|
|
|
|
project.container_repositories << container_repository
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { project.rename_repo }
|
|
|
|
|
2017-08-09 02:41:32 -04:00
|
|
|
it { expect { subject }.to raise_error(StandardError) }
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
end
|
2017-08-11 03:51:56 -04:00
|
|
|
|
|
|
|
describe '#pages_path' do
|
|
|
|
it 'returns a path where pages are stored' do
|
|
|
|
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
|
|
|
|
end
|
|
|
|
end
|
2017-09-06 01:16:26 -04:00
|
|
|
|
|
|
|
describe '#migrate_to_hashed_storage!' do
|
|
|
|
it 'returns true' do
|
|
|
|
expect(project.migrate_to_hashed_storage!).to be_truthy
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'flags as readonly' do
|
|
|
|
expect { project.migrate_to_hashed_storage! }.to change { project.repository_read_only }.to(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the project repo is in use' do
|
|
|
|
Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase
|
|
|
|
|
|
|
|
expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
|
|
|
|
|
|
|
|
project.migrate_to_hashed_storage!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'schedules ProjectMigrateHashedStorageWorker with delayed start when the wiki repo is in use' do
|
|
|
|
Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase
|
|
|
|
|
|
|
|
expect(ProjectMigrateHashedStorageWorker).to receive(:perform_in)
|
|
|
|
|
|
|
|
project.migrate_to_hashed_storage!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'schedules ProjectMigrateHashedStorageWorker' do
|
|
|
|
expect(ProjectMigrateHashedStorageWorker).to receive(:perform_async).with(project.id)
|
|
|
|
|
|
|
|
project.migrate_to_hashed_storage!
|
|
|
|
end
|
|
|
|
end
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'hashed storage' do
|
2017-08-07 05:07:42 -04:00
|
|
|
let(:project) { create(:project, :repository) }
|
2017-08-04 01:30:42 -04:00
|
|
|
let(:gitlab_shell) { Gitlab::Shell.new }
|
|
|
|
let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' }
|
|
|
|
|
|
|
|
before do
|
2017-08-07 05:07:42 -04:00
|
|
|
stub_application_setting(hashed_storage_enabled: true)
|
2017-08-04 01:30:42 -04:00
|
|
|
allow(Digest::SHA2).to receive(:hexdigest) { hash }
|
2017-08-11 03:51:56 -04:00
|
|
|
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
|
2017-09-06 01:16:26 -04:00
|
|
|
describe '#legacy_storage?' do
|
|
|
|
it 'returns false' do
|
|
|
|
expect(project.legacy_storage?).to be_falsey
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#hashed_storage?' do
|
|
|
|
it 'returns true' do
|
|
|
|
expect(project.hashed_storage?).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-04 01:30:42 -04:00
|
|
|
describe '#base_dir' do
|
|
|
|
it 'returns base_dir based on hash of project id' do
|
2017-08-17 11:39:18 -04:00
|
|
|
expect(project.base_dir).to eq('@hashed/6b/86')
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#disk_path' do
|
2017-08-11 03:51:56 -04:00
|
|
|
it 'returns disk_path based on hash of project id' do
|
2017-08-17 11:39:18 -04:00
|
|
|
hashed_path = '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b'
|
2017-08-04 01:30:42 -04:00
|
|
|
|
|
|
|
expect(project.disk_path).to eq(hashed_path)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-15 22:49:54 -04:00
|
|
|
describe '#ensure_storage_path_exists' do
|
2017-08-04 01:30:42 -04:00
|
|
|
it 'delegates to gitlab_shell to ensure namespace is created' do
|
2017-08-17 11:39:18 -04:00
|
|
|
expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, '@hashed/6b/86')
|
2017-08-04 01:30:42 -04:00
|
|
|
|
2017-08-15 22:49:54 -04:00
|
|
|
project.ensure_storage_path_exists
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#rename_repo' do
|
|
|
|
before do
|
|
|
|
# Project#gitlab_shell returns a new instance of Gitlab::Shell on every
|
|
|
|
# call. This makes testing a bit easier.
|
|
|
|
allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
|
|
|
|
allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'renames a repository' do
|
|
|
|
stub_container_registry_config(enabled: false)
|
|
|
|
|
|
|
|
expect(gitlab_shell).not_to receive(:mv_repository)
|
|
|
|
|
|
|
|
expect_any_instance_of(SystemHooksService)
|
|
|
|
.to receive(:execute_hooks_for)
|
|
|
|
.with(project, :rename)
|
|
|
|
|
|
|
|
expect_any_instance_of(Gitlab::UploadsTransfer)
|
|
|
|
.to receive(:rename_project)
|
|
|
|
.with('foo', project.path, project.namespace.full_path)
|
|
|
|
|
|
|
|
expect(project).to receive(:expire_caches_before_rename)
|
|
|
|
|
|
|
|
expect(project).to receive(:expires_full_path_cache)
|
|
|
|
|
|
|
|
project.rename_repo
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'container registry with images' do
|
|
|
|
let(:container_repository) { create(:container_repository) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
stub_container_registry_config(enabled: true)
|
|
|
|
stub_container_registry_tags(repository: :any, tags: ['tag'])
|
|
|
|
project.container_repositories << container_repository
|
|
|
|
end
|
|
|
|
|
|
|
|
subject { project.rename_repo }
|
|
|
|
|
2017-08-09 02:41:32 -04:00
|
|
|
it { expect { subject }.to raise_error(StandardError) }
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
|
|
|
end
|
2017-08-11 03:51:56 -04:00
|
|
|
|
|
|
|
describe '#pages_path' do
|
|
|
|
it 'returns a path where pages are stored' do
|
|
|
|
expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
|
|
|
|
end
|
|
|
|
end
|
2017-09-06 01:16:26 -04:00
|
|
|
|
|
|
|
describe '#migrate_to_hashed_storage!' do
|
|
|
|
it 'returns nil' do
|
|
|
|
expect(project.migrate_to_hashed_storage!).to be_nil
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not flag as readonly' do
|
|
|
|
expect { project.migrate_to_hashed_storage! }.not_to change { project.repository_read_only }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#gl_repository' do
|
|
|
|
let(:project) { create(:project) }
|
|
|
|
|
|
|
|
it 'delegates to Gitlab::GlRepository.gl_repository' do
|
|
|
|
expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true)
|
|
|
|
|
|
|
|
project.gl_repository(is_wiki: true)
|
|
|
|
end
|
2017-08-04 01:30:42 -04:00
|
|
|
end
|
2017-09-06 15:00:34 -04:00
|
|
|
|
|
|
|
describe '#has_ci?' do
|
|
|
|
set(:project) { create(:project) }
|
|
|
|
let(:repository) { double }
|
|
|
|
|
|
|
|
before do
|
|
|
|
expect(project).to receive(:repository) { repository }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when has .gitlab-ci.yml' do
|
|
|
|
before do
|
|
|
|
expect(repository).to receive(:gitlab_ci_yml) { 'content' }
|
|
|
|
end
|
|
|
|
|
|
|
|
it "CI is available" do
|
|
|
|
expect(project).to have_ci
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when there is no .gitlab-ci.yml' do
|
|
|
|
before do
|
|
|
|
expect(repository).to receive(:gitlab_ci_yml) { nil }
|
|
|
|
end
|
|
|
|
|
|
|
|
it "CI is not available" do
|
|
|
|
expect(project).not_to have_ci
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when auto devops is enabled' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "CI is available" do
|
|
|
|
expect(project).to have_ci
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#auto_devops_enabled?' do
|
|
|
|
set(:project) { create(:project) }
|
|
|
|
|
|
|
|
subject { project.auto_devops_enabled? }
|
|
|
|
|
|
|
|
context 'when enabled in settings' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'auto devops is implicitly enabled' do
|
|
|
|
expect(project.auto_devops).to be_nil
|
|
|
|
expect(project).to be_auto_devops_enabled
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when explicitly enabled' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "auto devops is enabled" do
|
|
|
|
expect(project).to be_auto_devops_enabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when explicitly disabled' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project, enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "auto devops is disabled" do
|
|
|
|
expect(project).not_to be_auto_devops_enabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when disabled in settings' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(auto_devops_enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'auto devops is implicitly disabled' do
|
|
|
|
expect(project.auto_devops).to be_nil
|
|
|
|
expect(project).not_to be_auto_devops_enabled
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when explicitly enabled' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "auto devops is enabled" do
|
|
|
|
expect(project).to be_auto_devops_enabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-07 09:08:51 -04:00
|
|
|
describe '#has_auto_devops_implicitly_disabled?' do
|
|
|
|
set(:project) { create(:project) }
|
|
|
|
|
|
|
|
context 'when enabled in settings' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when disabled in settings' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(auto_devops_enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'auto devops is implicitly disabled' do
|
|
|
|
expect(project).to have_auto_devops_implicitly_disabled
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when explicitly disabled' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project, enabled: false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when explicitly enabled' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not have auto devops implicitly disabled' do
|
|
|
|
expect(project).not_to have_auto_devops_implicitly_disabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-06 15:00:34 -04:00
|
|
|
context '#auto_devops_variables' do
|
|
|
|
set(:project) { create(:project) }
|
|
|
|
|
|
|
|
subject { project.auto_devops_variables }
|
|
|
|
|
|
|
|
context 'when enabled in settings' do
|
|
|
|
before do
|
|
|
|
stub_application_setting(auto_devops_enabled: true)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when domain is empty' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project, domain: nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'variables are empty' do
|
|
|
|
is_expected.to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when domain is configured' do
|
|
|
|
before do
|
|
|
|
create(:project_auto_devops, project: project, domain: 'example.com')
|
|
|
|
end
|
|
|
|
|
|
|
|
it "variables are not empty" do
|
|
|
|
is_expected.not_to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2017-09-12 09:54:25 -04:00
|
|
|
|
|
|
|
describe '#latest_successful_builds_for' do
|
|
|
|
let(:project) { build(:project) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:default_branch).and_return('master')
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'without a ref' do
|
|
|
|
it 'returns a pipeline for the default branch' do
|
|
|
|
expect(project)
|
|
|
|
.to receive(:latest_successful_pipeline_for_default_branch)
|
|
|
|
|
|
|
|
project.latest_successful_pipeline_for
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with the ref set to the default branch' do
|
|
|
|
it 'returns a pipeline for the default branch' do
|
|
|
|
expect(project)
|
|
|
|
.to receive(:latest_successful_pipeline_for_default_branch)
|
|
|
|
|
|
|
|
project.latest_successful_pipeline_for(project.default_branch)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a ref that is not the default branch' do
|
|
|
|
it 'returns the latest successful pipeline for the given ref' do
|
|
|
|
expect(project.pipelines).to receive(:latest_successful_for).with('foo')
|
|
|
|
|
|
|
|
project.latest_successful_pipeline_for('foo')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-29 20:20:56 -04:00
|
|
|
describe '#check_repository_path_availability' do
|
|
|
|
let(:project) { build(:project) }
|
|
|
|
|
|
|
|
it 'skips gitlab-shell exists?' do
|
|
|
|
project.skip_disk_validation = true
|
|
|
|
|
|
|
|
expect(project.gitlab_shell).not_to receive(:exists?)
|
|
|
|
expect(project.check_repository_path_availability).to be_truthy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-09-12 09:54:25 -04:00
|
|
|
describe '#latest_successful_pipeline_for_default_branch' do
|
|
|
|
let(:project) { build(:project) }
|
|
|
|
|
|
|
|
before do
|
|
|
|
allow(project).to receive(:default_branch).and_return('master')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'memoizes and returns the latest successful pipeline for the default branch' do
|
|
|
|
pipeline = double(:pipeline)
|
|
|
|
|
|
|
|
expect(project.pipelines).to receive(:latest_successful_for)
|
|
|
|
.with(project.default_branch)
|
|
|
|
.and_return(pipeline)
|
|
|
|
.once
|
|
|
|
|
|
|
|
2.times do
|
|
|
|
expect(project.latest_successful_pipeline_for_default_branch)
|
|
|
|
.to eq(pipeline)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2011-10-08 17:36:38 -04:00
|
|
|
end
|