85a539e8ff
Make Service.external_wikis return only active external wikis Fixes #20067. ## Does this MR meet the acceptance criteria? - [x] No CHANGELOG since it fixes a RC12 regression - Tests - [x] Added for this feature/bug - [ ] All builds are passing - [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides) - [x] Branch has no merge conflicts with `master` (if you do - rebase it please) - [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) See merge request !5387
1328 lines
43 KiB
Ruby
1328 lines
43 KiB
Ruby
require 'spec_helper'
|
|
|
|
describe Project, models: true do
|
|
describe 'associations' do
|
|
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) }
|
|
it { is_expected.to have_many(:events).dependent(:destroy) }
|
|
it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
|
|
it { is_expected.to have_many(:issues).dependent(:destroy) }
|
|
it { is_expected.to have_many(:milestones).dependent(:destroy) }
|
|
it { is_expected.to have_many(:project_members).dependent(:destroy) }
|
|
it { is_expected.to have_many(:users).through(:project_members) }
|
|
it { is_expected.to have_many(:requesters).dependent(:destroy) }
|
|
it { is_expected.to have_many(:notes).dependent(:destroy) }
|
|
it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
|
|
it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) }
|
|
it { is_expected.to have_many(:deploy_keys) }
|
|
it { is_expected.to have_many(:hooks).dependent(:destroy) }
|
|
it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
|
|
it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
|
|
it { is_expected.to have_one(:slack_service).dependent(:destroy) }
|
|
it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
|
|
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
|
|
it { is_expected.to have_many(:commit_statuses) }
|
|
it { is_expected.to have_many(:pipelines) }
|
|
it { is_expected.to have_many(:builds) }
|
|
it { is_expected.to have_many(:runner_projects) }
|
|
it { is_expected.to have_many(:runners) }
|
|
it { is_expected.to have_many(:variables) }
|
|
it { is_expected.to have_many(:triggers) }
|
|
it { is_expected.to have_many(:environments).dependent(:destroy) }
|
|
it { is_expected.to have_many(:deployments).dependent(:destroy) }
|
|
it { is_expected.to have_many(:todos).dependent(:destroy) }
|
|
|
|
describe '#members & #requesters' do
|
|
let(:project) { create(:project) }
|
|
let(:requester) { create(:user) }
|
|
let(:developer) { create(:user) }
|
|
before do
|
|
project.request_access(requester)
|
|
project.team << [developer, :developer]
|
|
end
|
|
|
|
describe '#members' do
|
|
it 'includes members and exclude requesters' do
|
|
member_user_ids = project.members.pluck(:user_id)
|
|
|
|
expect(member_user_ids).to include(developer.id)
|
|
expect(member_user_ids).not_to include(requester.id)
|
|
end
|
|
end
|
|
|
|
describe '#requesters' do
|
|
it 'does not include requesters' do
|
|
requester_user_ids = project.requesters.pluck(:user_id)
|
|
|
|
expect(requester_user_ids).to include(requester.id)
|
|
expect(requester_user_ids).not_to include(developer.id)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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) }
|
|
it { is_expected.to include_module(Referable) }
|
|
it { is_expected.to include_module(Sortable) }
|
|
end
|
|
|
|
describe 'validation' do
|
|
let!(:project) { create(:project) }
|
|
|
|
it { is_expected.to validate_presence_of(:name) }
|
|
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
|
|
it { is_expected.to validate_length_of(:name).is_within(0..255) }
|
|
|
|
it { is_expected.to validate_presence_of(:path) }
|
|
it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
|
|
it { is_expected.to validate_length_of(:path).is_within(0..255) }
|
|
it { is_expected.to validate_length_of(:description).is_within(0..2000) }
|
|
it { is_expected.to validate_presence_of(:creator) }
|
|
it { is_expected.to validate_presence_of(:namespace) }
|
|
it { is_expected.to validate_presence_of(:repository_storage) }
|
|
|
|
it 'should not allow new projects beyond user limits' do
|
|
project2 = build(:project)
|
|
allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
|
|
expect(project2).not_to be_valid
|
|
expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
|
|
end
|
|
|
|
describe 'wiki path conflict' do
|
|
context "when the new path has been used by the wiki of other Project" do
|
|
it 'should have an error on the name attribute' do
|
|
new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
|
|
|
|
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
|
|
it 'should have an error on the name attribute' do
|
|
project_with_wiki_suffix = create(:project, path: 'foo.wiki')
|
|
new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
|
|
|
|
expect(new_project).not_to be_valid
|
|
expect(new_project.errors[:name].first).to eq('has already been taken')
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'repository storages inclussion' do
|
|
let(:project2) { build(:project, repository_storage: 'missing') }
|
|
|
|
before do
|
|
storages = { 'custom' => 'tmp/tests/custom_repositories' }
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
end
|
|
|
|
it "should not allow repository storages that don't match a label in the configuration" do
|
|
expect(project2).not_to be_valid
|
|
expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
|
|
end
|
|
end
|
|
|
|
it 'does not allow an invalid URI as import_url' do
|
|
project2 = build(:project, import_url: 'invalid://')
|
|
|
|
expect(project2).not_to be_valid
|
|
end
|
|
|
|
it 'does allow a valid URI as import_url' do
|
|
project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
|
|
|
|
expect(project2).to be_valid
|
|
end
|
|
|
|
it 'allows an empty URI' do
|
|
project2 = build(:project, import_url: '')
|
|
|
|
expect(project2).to be_valid
|
|
end
|
|
|
|
it 'does not produce import data on an empty URI' do
|
|
project2 = build(:project, import_url: '')
|
|
|
|
expect(project2.import_data).to be_nil
|
|
end
|
|
|
|
it 'does not produce import data on an invalid URI' do
|
|
project2 = build(:project, import_url: 'test://')
|
|
|
|
expect(project2.import_data).to be_nil
|
|
end
|
|
end
|
|
|
|
describe 'default_scope' do
|
|
it 'excludes projects pending deletion from the results' do
|
|
project = create(:empty_project)
|
|
create(:empty_project, pending_delete: true)
|
|
|
|
expect(Project.all).to eq [project]
|
|
end
|
|
end
|
|
|
|
describe 'project token' do
|
|
it 'should set an random token if none provided' do
|
|
project = FactoryGirl.create :empty_project, runners_token: ''
|
|
expect(project.runners_token).not_to eq('')
|
|
end
|
|
|
|
it 'should not set an random toke if one provided' do
|
|
project = FactoryGirl.create :empty_project, runners_token: 'my-token'
|
|
expect(project.runners_token).to eq('my-token')
|
|
end
|
|
end
|
|
|
|
describe 'Respond to' do
|
|
it { is_expected.to respond_to(:url_to_repo) }
|
|
it { is_expected.to respond_to(:repo_exists?) }
|
|
it { is_expected.to respond_to(:update_merge_requests) }
|
|
it { is_expected.to respond_to(:execute_hooks) }
|
|
it { is_expected.to respond_to(:owner) }
|
|
it { is_expected.to respond_to(:path_with_namespace) }
|
|
end
|
|
|
|
describe '#name_with_namespace' do
|
|
let(:project) { build_stubbed(:empty_project) }
|
|
|
|
it { expect(project.name_with_namespace).to eq "#{project.namespace.human_name} / #{project.name}" }
|
|
it { expect(project.human_name).to eq project.name_with_namespace }
|
|
end
|
|
|
|
describe '#to_reference' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
it 'returns a String reference to the object' do
|
|
expect(project.to_reference).to eq project.path_with_namespace
|
|
end
|
|
end
|
|
|
|
describe '#repository_storage_path' do
|
|
let(:project) { create(:project, repository_storage: 'custom') }
|
|
|
|
before do
|
|
FileUtils.mkdir('tmp/tests/custom_repositories')
|
|
storages = { 'custom' => 'tmp/tests/custom_repositories' }
|
|
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
|
|
|
|
it 'should return valid url to repo' do
|
|
project = Project.new(path: 'somewhere')
|
|
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
|
|
end
|
|
|
|
describe "#web_url" do
|
|
let(:project) { create(:empty_project, path: "somewhere") }
|
|
|
|
it 'returns the full web URL for this repo' do
|
|
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
|
|
end
|
|
end
|
|
|
|
describe "#web_url_without_protocol" do
|
|
let(:project) { create(:empty_project, path: "somewhere") }
|
|
|
|
it 'returns the web URL without the protocol for this repo' do
|
|
expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/#{project.namespace.path}/somewhere")
|
|
end
|
|
end
|
|
|
|
describe 'last_activity methods' do
|
|
let(:project) { create(:project) }
|
|
let(:last_event) { double(created_at: Time.now) }
|
|
|
|
describe 'last_activity' do
|
|
it 'should alias last_activity to last_event' do
|
|
allow(project).to receive(:last_event).and_return(last_event)
|
|
expect(project.last_activity).to eq(last_event)
|
|
end
|
|
end
|
|
|
|
describe 'last_activity_date' do
|
|
it 'returns the creation date of the project\'s last event if present' do
|
|
create(:event, project: project)
|
|
expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i)
|
|
end
|
|
|
|
it 'returns the project\'s last update date if it has no events' do
|
|
expect(project.last_activity_date).to eq(project.updated_at)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#get_issue' do
|
|
let(:project) { create(:empty_project) }
|
|
let!(:issue) { create(:issue, project: project) }
|
|
|
|
context 'with default issues tracker' do
|
|
it 'returns an issue' do
|
|
expect(project.get_issue(issue.iid)).to eq issue
|
|
end
|
|
|
|
it 'returns count of open issues' do
|
|
expect(project.open_issues_count).to eq(1)
|
|
end
|
|
|
|
it 'returns nil when no issue found' do
|
|
expect(project.get_issue(999)).to be_nil
|
|
end
|
|
end
|
|
|
|
context 'with external issues tracker' do
|
|
before do
|
|
allow(project).to receive(:default_issues_tracker?).and_return(false)
|
|
end
|
|
|
|
it 'returns an ExternalIssue' do
|
|
issue = project.get_issue('FOO-1234')
|
|
expect(issue).to be_kind_of(ExternalIssue)
|
|
expect(issue.iid).to eq 'FOO-1234'
|
|
expect(issue.project).to eq project
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#issue_exists?' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
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
|
|
|
|
describe '#update_merge_requests' do
|
|
let(:project) { create(:project) }
|
|
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
|
|
let(:key) { create(:key, user_id: project.owner.id) }
|
|
let(:prev_commit_id) { merge_request.commits.last.id }
|
|
let(:commit_id) { merge_request.commits.first.id }
|
|
|
|
it 'should close merge request if last commit from source branch was pushed to target branch' do
|
|
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user)
|
|
merge_request.reload
|
|
expect(merge_request.merged?).to be_truthy
|
|
end
|
|
|
|
it 'should update merge request commits with new one if pushed to source branch' do
|
|
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user)
|
|
merge_request.reload
|
|
expect(merge_request.diff_head_sha).to eq(commit_id)
|
|
end
|
|
end
|
|
|
|
describe '.find_with_namespace' do
|
|
context 'with namespace' do
|
|
before do
|
|
@group = create :group, name: 'gitlab'
|
|
@project = create(:project, name: 'gitlabhq', namespace: @group)
|
|
end
|
|
|
|
it { expect(Project.find_with_namespace('gitlab/gitlabhq')).to eq(@project) }
|
|
it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) }
|
|
it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil }
|
|
end
|
|
|
|
context 'when multiple projects using a similar name exist' do
|
|
let(:group) { create(:group, name: 'gitlab') }
|
|
|
|
let!(:project1) do
|
|
create(:empty_project, name: 'gitlab1', path: 'gitlab', namespace: group)
|
|
end
|
|
|
|
let!(:project2) do
|
|
create(:empty_project, name: 'gitlab2', path: 'GITLAB', namespace: group)
|
|
end
|
|
|
|
it 'returns the row where the path matches literally' do
|
|
expect(Project.find_with_namespace('gitlab/GITLAB')).to eq(project2)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#to_param' do
|
|
context 'with namespace' do
|
|
before do
|
|
@group = create :group, name: 'gitlab'
|
|
@project = create(:project, name: 'gitlabhq', namespace: @group)
|
|
end
|
|
|
|
it { expect(@project.to_param).to eq('gitlabhq') }
|
|
end
|
|
end
|
|
|
|
describe '#repository' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'returns valid repo' do
|
|
expect(project.repository).to be_kind_of(Repository)
|
|
end
|
|
end
|
|
|
|
describe '#default_issues_tracker?' do
|
|
let(:project) { create(:project) }
|
|
let(:ext_project) { create(:redmine_project) }
|
|
|
|
it "should be true if used internal tracker" do
|
|
expect(project.default_issues_tracker?).to be_truthy
|
|
end
|
|
|
|
it "should be false if used other tracker" do
|
|
expect(ext_project.default_issues_tracker?).to be_falsey
|
|
end
|
|
end
|
|
|
|
describe '#external_issue_tracker' do
|
|
let(:project) { create(:project) }
|
|
let(:ext_project) { create(:redmine_project) }
|
|
|
|
context 'on existing projects with no value for has_external_issue_tracker' do
|
|
before(:each) do
|
|
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
|
|
project.build_missing_services
|
|
project.reload
|
|
|
|
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
|
|
ext_project.build_missing_services
|
|
ext_project.reload
|
|
|
|
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
|
|
|
|
describe '#cache_has_external_issue_tracker' do
|
|
let(:project) { create(:project) }
|
|
|
|
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
|
|
|
|
describe '#external_wiki' do
|
|
let(:project) { create(:project) }
|
|
|
|
context 'with an active external wiki' do
|
|
before do
|
|
create(:service, project: project, type: 'ExternalWikiService', active: true)
|
|
project.external_wiki
|
|
end
|
|
|
|
it 'sets :has_external_wiki as true' do
|
|
expect(project.has_external_wiki).to be(true)
|
|
end
|
|
|
|
it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
|
|
expect(project.has_external_wiki).to be(true)
|
|
|
|
project.services.external_wikis.first.destroy
|
|
|
|
expect(project.has_external_wiki).to be(false)
|
|
end
|
|
end
|
|
|
|
context 'with an inactive external wiki' do
|
|
before do
|
|
create(:service, project: project, type: 'ExternalWikiService', active: false)
|
|
end
|
|
|
|
it 'sets :has_external_wiki as false' do
|
|
expect(project.has_external_wiki).to be(false)
|
|
end
|
|
end
|
|
|
|
context 'with no external wiki' do
|
|
before do
|
|
project.external_wiki
|
|
end
|
|
|
|
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
|
|
end
|
|
end
|
|
|
|
describe '#open_branches' do
|
|
let(:project) { create(:project) }
|
|
|
|
before do
|
|
project.protected_branches.create(name: 'master')
|
|
end
|
|
|
|
it { expect(project.open_branches.map(&:name)).to include('feature') }
|
|
it { expect(project.open_branches.map(&:name)).not_to include('master') }
|
|
|
|
it "includes branches matching a protected branch wildcard" do
|
|
expect(project.open_branches.map(&:name)).to include('feature')
|
|
|
|
create(:protected_branch, name: 'feat*', project: project)
|
|
|
|
expect(Project.find(project.id).open_branches.map(&:name)).to include('feature')
|
|
end
|
|
end
|
|
|
|
describe '#star_count' do
|
|
it 'counts stars from multiple users' do
|
|
user1 = create :user
|
|
user2 = create :user
|
|
project = create :project, :public
|
|
|
|
expect(project.star_count).to eq(0)
|
|
|
|
user1.toggle_star(project)
|
|
expect(project.reload.star_count).to eq(1)
|
|
|
|
user2.toggle_star(project)
|
|
project.reload
|
|
expect(project.reload.star_count).to eq(2)
|
|
|
|
user1.toggle_star(project)
|
|
project.reload
|
|
expect(project.reload.star_count).to eq(1)
|
|
|
|
user2.toggle_star(project)
|
|
project.reload
|
|
expect(project.reload.star_count).to eq(0)
|
|
end
|
|
|
|
it 'counts stars on the right project' do
|
|
user = create :user
|
|
project1 = create :project, :public
|
|
project2 = create :project, :public
|
|
|
|
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)
|
|
end
|
|
end
|
|
|
|
describe '#avatar_type' do
|
|
let(:project) { create(:project) }
|
|
|
|
it 'should be true if avatar is image' do
|
|
project.update_attribute(:avatar, 'uploads/avatar.png')
|
|
expect(project.avatar_type).to be_truthy
|
|
end
|
|
|
|
it 'should be false if avatar is html page' do
|
|
project.update_attribute(:avatar, 'uploads/avatar.html')
|
|
expect(project.avatar_type).to eq(['only images allowed'])
|
|
end
|
|
end
|
|
|
|
describe '#avatar_url' do
|
|
subject { project.avatar_url }
|
|
|
|
let(:project) { create(:project) }
|
|
|
|
context 'When avatar file is uploaded' do
|
|
before do
|
|
project.update_columns(avatar: 'uploads/avatar.png')
|
|
allow(project.avatar).to receive(:present?) { true }
|
|
end
|
|
|
|
let(:avatar_path) do
|
|
"/uploads/project/avatar/#{project.id}/uploads/avatar.png"
|
|
end
|
|
|
|
it { should eq "http://localhost#{avatar_path}" }
|
|
end
|
|
|
|
context 'When avatar file in git' do
|
|
before do
|
|
allow(project).to receive(:avatar_in_git) { true }
|
|
end
|
|
|
|
let(:avatar_path) do
|
|
"/#{project.namespace.name}/#{project.path}/avatar"
|
|
end
|
|
|
|
it { should eq "http://localhost#{avatar_path}" }
|
|
end
|
|
|
|
context 'when git repo is empty' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
it { should eq nil }
|
|
end
|
|
end
|
|
|
|
describe '#pipeline' do
|
|
let(:project) { create :project }
|
|
let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
|
|
|
|
subject { project.pipeline(pipeline.sha, 'master') }
|
|
|
|
it { is_expected.to eq(pipeline) }
|
|
|
|
context 'return latest' do
|
|
let(:pipeline2) { create :ci_pipeline, project: project, ref: 'master' }
|
|
|
|
before do
|
|
pipeline
|
|
pipeline2
|
|
end
|
|
|
|
it { is_expected.to eq(pipeline2) }
|
|
end
|
|
end
|
|
|
|
describe '#builds_enabled' do
|
|
let(:project) { create :project }
|
|
|
|
before { project.builds_enabled = true }
|
|
|
|
subject { project.builds_enabled }
|
|
|
|
it { expect(project.builds_enabled?).to be_truthy }
|
|
end
|
|
|
|
describe '.trending' do
|
|
let(:group) { create(:group, :public) }
|
|
let(:project1) { create(:empty_project, :public, group: group) }
|
|
let(:project2) { create(:empty_project, :public, group: group) }
|
|
|
|
before do
|
|
2.times do
|
|
create(:note_on_commit, project: project1)
|
|
end
|
|
|
|
create(:note_on_commit, project: project2)
|
|
end
|
|
|
|
describe 'without an explicit start date' do
|
|
subject { described_class.trending.to_a }
|
|
|
|
it 'sorts Projects by the amount of notes in descending order' do
|
|
expect(subject).to eq([project1, project2])
|
|
end
|
|
end
|
|
|
|
describe 'with an explicit start date' do
|
|
let(:date) { 2.months.ago }
|
|
|
|
subject { described_class.trending(date).to_a }
|
|
|
|
before do
|
|
2.times do
|
|
# Little fix for special issue related to Fractional Seconds support for MySQL.
|
|
# See: https://github.com/rails/rails/pull/14359/files
|
|
create(:note_on_commit, project: project2, created_at: date + 1)
|
|
end
|
|
end
|
|
|
|
it 'sorts Projects by the amount of notes in descending order' do
|
|
expect(subject).to eq([project2, project1])
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '.visible_to_user' do
|
|
let!(:project) { create(:project, :private) }
|
|
let!(:user) { create(:user) }
|
|
|
|
subject { described_class.visible_to_user(user) }
|
|
|
|
describe 'when a user has access to a project' do
|
|
before do
|
|
project.team.add_user(user, Gitlab::Access::MASTER)
|
|
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
|
|
|
|
context 'repository storage by default' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
subject { project.repository_storage }
|
|
|
|
before do
|
|
storages = { 'alternative_storage' => '/some/path' }
|
|
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
|
|
stub_application_setting(repository_storage: 'alternative_storage')
|
|
allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(true)
|
|
end
|
|
|
|
it { is_expected.to eq('alternative_storage') }
|
|
end
|
|
|
|
context 'shared runners by default' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
subject { project.shared_runners_enabled }
|
|
|
|
context 'are enabled' do
|
|
before { stub_application_setting(shared_runners_enabled: true) }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'are disabled' do
|
|
before { stub_application_setting(shared_runners_enabled: false) }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#any_runners' do
|
|
let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
|
|
let(:specific_runner) { create(:ci_runner) }
|
|
let(:shared_runner) { create(:ci_runner, :shared) }
|
|
|
|
context 'for shared runners disabled' do
|
|
let(:shared_runners_enabled) { false }
|
|
|
|
it 'there are no runners available' do
|
|
expect(project.any_runners?).to be_falsey
|
|
end
|
|
|
|
it 'there is a specific runner' do
|
|
project.runners << specific_runner
|
|
expect(project.any_runners?).to be_truthy
|
|
end
|
|
|
|
it 'there is a shared runner, but they are prohibited to use' do
|
|
shared_runner
|
|
expect(project.any_runners?).to be_falsey
|
|
end
|
|
|
|
it 'checks the presence of specific runner' do
|
|
project.runners << specific_runner
|
|
expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
|
|
end
|
|
end
|
|
|
|
context 'for shared runners enabled' do
|
|
let(:shared_runners_enabled) { true }
|
|
|
|
it 'there is a shared runner' do
|
|
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
|
|
|
|
describe '#visibility_level_allowed?' do
|
|
let(:project) { create(:project, :internal) }
|
|
|
|
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
|
|
let(:project) { create(:project, :internal) }
|
|
let(:forked_project) { create(:project, forked_from_project: project) }
|
|
|
|
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
|
|
end
|
|
|
|
describe '.search' do
|
|
let(:project) { create(:project, description: 'kitten mittens') }
|
|
|
|
it 'returns projects with a matching name' do
|
|
expect(described_class.search(project.name)).to eq([project])
|
|
end
|
|
|
|
it 'returns projects with a partially matching name' do
|
|
expect(described_class.search(project.name[0..2])).to eq([project])
|
|
end
|
|
|
|
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
|
|
|
|
it 'returns projects when eager loading namespaces' do
|
|
relation = described_class.all.includes(:namespace)
|
|
|
|
expect(relation.search(project.namespace.name)).to eq([project])
|
|
end
|
|
end
|
|
|
|
describe '#rename_repo' do
|
|
let(:project) { create(:project) }
|
|
let(:gitlab_shell) { Gitlab::Shell.new }
|
|
|
|
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
|
|
ns = project.namespace_dir
|
|
|
|
expect(gitlab_shell).to receive(:mv_repository).
|
|
ordered.
|
|
with(project.repository_storage_path, "#{ns}/foo", "#{ns}/#{project.path}").
|
|
and_return(true)
|
|
|
|
expect(gitlab_shell).to receive(:mv_repository).
|
|
ordered.
|
|
with(project.repository_storage_path, "#{ns}/foo.wiki", "#{ns}/#{project.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, ns)
|
|
|
|
expect(project).to receive(:expire_caches_before_rename)
|
|
|
|
project.rename_repo
|
|
end
|
|
|
|
context 'container registry with tags' do
|
|
before do
|
|
stub_container_registry_config(enabled: true)
|
|
stub_container_registry_tags('tag')
|
|
end
|
|
|
|
subject { project.rename_repo }
|
|
|
|
it { expect{subject}.to raise_error(Exception) }
|
|
end
|
|
end
|
|
|
|
describe '#expire_caches_before_rename' do
|
|
let(:project) { create(:project) }
|
|
let(:repo) { double(:repo, exists?: true) }
|
|
let(:wiki) { double(:wiki, exists?: true) }
|
|
|
|
it 'expires the caches of the repository and wiki' do
|
|
allow(Repository).to receive(:new).
|
|
with('foo', project).
|
|
and_return(repo)
|
|
|
|
allow(Repository).to receive(:new).
|
|
with('foo.wiki', project).
|
|
and_return(wiki)
|
|
|
|
expect(repo).to receive(:before_delete)
|
|
expect(wiki).to receive(:before_delete)
|
|
|
|
project.expire_caches_before_rename('foo')
|
|
end
|
|
end
|
|
|
|
describe '.search_by_title' do
|
|
let(:project) { create(:project, name: 'kittens') }
|
|
|
|
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
|
|
|
|
context 'when checking projects from groups' do
|
|
let(:private_group) { create(:group, visibility_level: 0) }
|
|
let(:internal_group) { create(:group, visibility_level: 10) }
|
|
|
|
let(:private_project) { create :project, :private, group: private_group }
|
|
let(:internal_project) { create :project, :internal, group: internal_group }
|
|
|
|
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
|
|
|
|
describe '#create_repository' do
|
|
let(:project) { create(:project) }
|
|
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
|
|
expect(shell).to receive(:add_repository).
|
|
with(project.repository_storage_path, project.path_with_namespace).
|
|
and_return(true)
|
|
|
|
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
|
|
expect(shell).to receive(:add_repository).
|
|
with(project.repository_storage_path, project.path_with_namespace).
|
|
and_return(false)
|
|
|
|
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
|
|
|
|
describe '#protected_branch?' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
it 'returns true when the branch matches a protected branch via direct match' do
|
|
project.protected_branches.create!(name: 'foo')
|
|
|
|
expect(project.protected_branch?('foo')).to eq(true)
|
|
end
|
|
|
|
it 'returns true when the branch matches a protected branch via wildcard match' do
|
|
project.protected_branches.create!(name: 'production/*')
|
|
|
|
expect(project.protected_branch?('production/some-branch')).to eq(true)
|
|
end
|
|
|
|
it 'returns false when the branch does not match a protected branch via direct match' do
|
|
expect(project.protected_branch?('foo')).to eq(false)
|
|
end
|
|
|
|
it 'returns false when the branch does not match a protected branch via wildcard match' do
|
|
project.protected_branches.create!(name: 'production/*')
|
|
|
|
expect(project.protected_branch?('staging/some-branch')).to eq(false)
|
|
end
|
|
end
|
|
|
|
describe "#developers_can_push_to_protected_branch?" do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
context "when the branch matches a protected branch via direct match" do
|
|
it "returns true if 'Developers can Push' is turned on" do
|
|
create(:protected_branch, name: "production", project: project, developers_can_push: true)
|
|
|
|
expect(project.developers_can_push_to_protected_branch?('production')).to be true
|
|
end
|
|
|
|
it "returns false if 'Developers can Push' is turned off" do
|
|
create(:protected_branch, name: "production", project: project, developers_can_push: false)
|
|
|
|
expect(project.developers_can_push_to_protected_branch?('production')).to be false
|
|
end
|
|
end
|
|
|
|
context "when the branch matches a protected branch via wilcard match" do
|
|
it "returns true if 'Developers can Push' is turned on" do
|
|
create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
|
|
|
|
expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true
|
|
end
|
|
|
|
it "returns false if 'Developers can Push' is turned off" do
|
|
create(:protected_branch, name: "production/*", project: project, developers_can_push: false)
|
|
|
|
expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false
|
|
end
|
|
end
|
|
|
|
context "when the branch does not match a protected branch" do
|
|
it "returns false" do
|
|
create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
|
|
|
|
expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#container_registry_path_with_namespace' do
|
|
let(:project) { create(:empty_project, path: 'PROJECT') }
|
|
|
|
subject { project.container_registry_path_with_namespace }
|
|
|
|
it { is_expected.not_to eq(project.path_with_namespace) }
|
|
it { is_expected.to eq(project.path_with_namespace.downcase) }
|
|
end
|
|
|
|
describe '#container_registry_repository' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
before { stub_container_registry_config(enabled: true) }
|
|
|
|
subject { project.container_registry_repository }
|
|
|
|
it { is_expected.not_to be_nil }
|
|
end
|
|
|
|
describe '#container_registry_repository_url' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
subject { project.container_registry_repository_url }
|
|
|
|
before { stub_container_registry_config(**registry_settings) }
|
|
|
|
context 'for enabled registry' do
|
|
let(:registry_settings) do
|
|
{
|
|
enabled: true,
|
|
host_port: 'example.com',
|
|
}
|
|
end
|
|
|
|
it { is_expected.not_to be_nil }
|
|
end
|
|
|
|
context 'for disabled registry' do
|
|
let(:registry_settings) do
|
|
{
|
|
enabled: false
|
|
}
|
|
end
|
|
|
|
it { is_expected.to be_nil }
|
|
end
|
|
end
|
|
|
|
describe '#has_container_registry_tags?' do
|
|
let(:project) { create(:empty_project) }
|
|
|
|
subject { project.has_container_registry_tags? }
|
|
|
|
context 'for enabled registry' do
|
|
before { stub_container_registry_config(enabled: true) }
|
|
|
|
context 'with tags' do
|
|
before { stub_container_registry_tags('test', 'test2') }
|
|
|
|
it { is_expected.to be_truthy }
|
|
end
|
|
|
|
context 'when no tags' do
|
|
before { stub_container_registry_tags }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
context 'for disabled registry' do
|
|
before { stub_container_registry_config(enabled: false) }
|
|
|
|
it { is_expected.to be_falsey }
|
|
end
|
|
end
|
|
|
|
describe '#latest_successful_builds_for' do
|
|
def create_pipeline(status = 'success')
|
|
create(:ci_pipeline, project: project,
|
|
sha: project.commit.sha,
|
|
ref: project.default_branch,
|
|
status: status)
|
|
end
|
|
|
|
def create_build(new_pipeline = pipeline, name = 'test')
|
|
create(:ci_build, :success, :artifacts,
|
|
pipeline: new_pipeline,
|
|
status: new_pipeline.status,
|
|
name: name)
|
|
end
|
|
|
|
let(:project) { create(:project) }
|
|
let(:pipeline) { create_pipeline }
|
|
|
|
context 'with many builds' do
|
|
it 'gives the latest builds from latest pipeline' do
|
|
pipeline1 = create_pipeline
|
|
pipeline2 = create_pipeline
|
|
build1_p2 = create_build(pipeline2, 'test')
|
|
create_build(pipeline1, 'test')
|
|
create_build(pipeline1, 'test2')
|
|
build2_p2 = create_build(pipeline2, 'test2')
|
|
|
|
latest_builds = project.latest_successful_builds_for
|
|
|
|
expect(latest_builds).to contain_exactly(build2_p2, build1_p2)
|
|
end
|
|
end
|
|
|
|
context 'with succeeded pipeline' do
|
|
let!(:build) { create_build }
|
|
|
|
context 'standalone pipeline' do
|
|
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')
|
|
|
|
expect(builds).to be_kind_of(ActiveRecord::Relation)
|
|
expect(builds).to be_empty
|
|
end
|
|
end
|
|
|
|
context 'with some pending pipeline' do
|
|
before do
|
|
create_build(create_pipeline('pending'))
|
|
end
|
|
|
|
it 'gives the latest build from latest pipeline' do
|
|
latest_build = project.latest_successful_builds_for
|
|
|
|
expect(latest_build).to contain_exactly(build)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'with pending pipeline' do
|
|
before do
|
|
pipeline.update(status: 'pending')
|
|
create_build(pipeline)
|
|
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
|
|
|
|
describe '.where_paths_in' do
|
|
context 'without any paths' do
|
|
it 'returns an empty relation' do
|
|
expect(Project.where_paths_in([])).to eq([])
|
|
end
|
|
end
|
|
|
|
context 'without any valid paths' do
|
|
it 'returns an empty relation' do
|
|
expect(Project.where_paths_in(%w[foo])).to eq([])
|
|
end
|
|
end
|
|
|
|
context 'with valid paths' do
|
|
let!(:project1) { create(:project) }
|
|
let!(:project2) { create(:project) }
|
|
|
|
it 'returns the projects matching the paths' do
|
|
projects = Project.where_paths_in([project1.path_with_namespace,
|
|
project2.path_with_namespace])
|
|
|
|
expect(projects).to contain_exactly(project1, project2)
|
|
end
|
|
|
|
it 'returns projects regardless of the casing of paths' do
|
|
projects = Project.where_paths_in([project1.path_with_namespace.upcase,
|
|
project2.path_with_namespace.upcase])
|
|
|
|
expect(projects).to contain_exactly(project1, project2)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'authorized_for_user' do
|
|
let(:group) { create(:group) }
|
|
let(:developer) { create(:user) }
|
|
let(:master) { create(:user) }
|
|
let(:personal_project) { create(:project, namespace: developer.namespace) }
|
|
let(:group_project) { create(:project, namespace: group) }
|
|
let(:members_project) { create(:project) }
|
|
let(:shared_project) { create(:project) }
|
|
|
|
before do
|
|
group.add_master(master)
|
|
group.add_developer(developer)
|
|
|
|
members_project.team << [developer, :developer]
|
|
members_project.team << [master, :master]
|
|
|
|
create(:project_group_link, project: shared_project, group: group)
|
|
end
|
|
|
|
it 'returns false for no user' do
|
|
expect(personal_project.authorized_for_user?(nil)).to be(false)
|
|
end
|
|
|
|
it 'returns true for personal projects of the user' do
|
|
expect(personal_project.authorized_for_user?(developer)).to be(true)
|
|
end
|
|
|
|
it 'returns true for projects of groups the user is a member of' do
|
|
expect(group_project.authorized_for_user?(developer)).to be(true)
|
|
end
|
|
|
|
it 'returns true for projects for which the user is a member of' do
|
|
expect(members_project.authorized_for_user?(developer)).to be(true)
|
|
end
|
|
|
|
it 'returns true for projects shared on a group the user is a member of' do
|
|
expect(shared_project.authorized_for_user?(developer)).to be(true)
|
|
end
|
|
|
|
it 'checks for the correct minimum level access' do
|
|
expect(group_project.authorized_for_user?(developer, Gitlab::Access::MASTER)).to be(false)
|
|
expect(group_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
|
|
expect(members_project.authorized_for_user?(developer, Gitlab::Access::MASTER)).to be(false)
|
|
expect(members_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
|
|
expect(shared_project.authorized_for_user?(developer, Gitlab::Access::MASTER)).to be(false)
|
|
expect(shared_project.authorized_for_user?(master, Gitlab::Access::MASTER)).to be(true)
|
|
end
|
|
end
|
|
end
|