diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2018-03-07 09:48:44 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2018-03-07 09:48:44 +0000 |
commit | 2b8e9add859f66f32c76948ed7b95f783188695c (patch) | |
tree | ad26b9c897d3a32c5665e57383b1845bc7f4b2cf /spec | |
parent | 8975bd3a66d726e6f5ff85d6c55a707d5c7dceb6 (diff) | |
parent | 95016507d49c3099afde0ef3909377bf70061dc3 (diff) | |
download | gitlab-ce-2b8e9add859f66f32c76948ed7b95f783188695c.tar.gz |
[ci skip] Merge branch 'master' into 43770-change-clear-runners-cache-ujs-action-to-an-axios-request
* master: (97 commits)
Remove common_vue bundle
Gitlab::Utils - backport `.ensure_array_from_string` from EE
Fix false positive spec of using digest instead of hexdigest
Change queue namespace of UpdateHeadPipelineForMergeRequestWorker
prefer checking explicitly for null
replace fixture with simple mount point
add new tests for metrics dashboard changes
Set sha256 checksum when archiving traces
Move update_head_pipeline_for_merge_request queue to pipeline_processing namespace
Docs guidelines: cherry-picking commits from CE to EE
CI charts now include the current day
New .dedicated-no-docs-pull-cache-job and .no-db-job definitions
Add date calls in .gitlab-ci.yml to see how long each step take
move misplaced entry point
Arg.id via method pointer
Backport description composition from EE
Fix a typo, a conflict and improve documentation
Remove extra breadcrumb on tags
Lazy fetch parent commits for a commit
Fix Gitaly n+1 in NetworkController#show
...
Diffstat (limited to 'spec')
76 files changed, 2184 insertions, 801 deletions
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb new file mode 100644 index 00000000000..0f5bde62006 --- /dev/null +++ b/spec/controllers/groups/boards_controller_spec.rb @@ -0,0 +1,136 @@ +require 'spec_helper' + +describe Groups::BoardsController do + let(:group) { create(:group) } + let(:user) { create(:user) } + + before do + group.add_master(user) + sign_in(user) + end + + describe 'GET index' do + it 'creates a new board when group does not have one' do + expect { list_boards }.to change(group.boards, :count).by(1) + end + + context 'when format is HTML' do + it 'renders template' do + list_boards + + expect(response).to render_template :index + expect(response.content_type).to eq 'text/html' + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false) + end + + it 'returns a not found 404 response' do + list_boards + + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'text/html' + end + end + end + + context 'when format is JSON' do + it 'return an array with one group board' do + create(:board, group: group) + + list_boards format: :json + + parsed_response = JSON.parse(response.body) + + expect(response).to match_response_schema('boards') + expect(parsed_response.length).to eq 1 + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false) + end + + it 'returns a not found 404 response' do + list_boards format: :json + + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'application/json' + end + end + end + + def list_boards(format: :html) + get :index, group_id: group, format: format + end + end + + describe 'GET show' do + let!(:board) { create(:board, group: group) } + + context 'when format is HTML' do + it 'renders template' do + read_board board: board + + expect(response).to render_template :show + expect(response.content_type).to eq 'text/html' + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false) + end + + it 'returns a not found 404 response' do + read_board board: board + + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'text/html' + end + end + end + + context 'when format is JSON' do + it 'returns project board' do + read_board board: board, format: :json + + expect(response).to match_response_schema('board') + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false) + end + + it 'returns a not found 404 response' do + read_board board: board, format: :json + + expect(response).to have_gitlab_http_status(404) + expect(response.content_type).to eq 'application/json' + end + end + end + + context 'when board does not belong to group' do + it 'returns a not found 404 response' do + another_board = create(:board) + + read_board board: another_board + + expect(response).to have_gitlab_http_status(404) + end + end + + def read_board(board:, format: :html) + get :show, group_id: group, + id: board.to_param, + format: format + end + end +end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 734396ddf7b..3b9e06cb5ad 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -407,10 +407,43 @@ describe Projects::BranchesController do get :index, namespace_id: project.namespace, project_id: project, + state: 'all', format: :html expect(response).to have_gitlab_http_status(200) end end + + context 'when depreated sort/search/page parameters are specified' do + it 'returns with a status 301 when sort specified' do + get :index, + namespace_id: project.namespace, + project_id: project, + sort: 'updated_asc', + format: :html + + expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') + end + + it 'returns with a status 301 when search specified' do + get :index, + namespace_id: project.namespace, + project_id: project, + search: 'feature', + format: :html + + expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') + end + + it 'returns with a status 301 when page specified' do + get :index, + namespace_id: project.namespace, + project_id: project, + page: 2, + format: :html + + expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') + end + end end end diff --git a/spec/controllers/projects/deployments_controller_spec.rb b/spec/controllers/projects/deployments_controller_spec.rb index 73e7921fab7..6c67dfde63a 100644 --- a/spec/controllers/projects/deployments_controller_spec.rb +++ b/spec/controllers/projects/deployments_controller_spec.rb @@ -129,10 +129,10 @@ describe Projects::DeploymentsController do end context 'when metrics are enabled' do - let(:prometheus_service) { double('prometheus_service') } + let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } before do - allow(deployment.project).to receive(:prometheus_service).and_return(prometheus_service) + allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter) end context 'when environment has no metrics' do diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb index f17f819feee..b2b245dba90 100644 --- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb +++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb @@ -4,21 +4,22 @@ describe Projects::Prometheus::MetricsController do let(:user) { create(:user) } let(:project) { create(:project) } - let(:prometheus_service) { double('prometheus_service') } + let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } before do - allow(controller).to receive(:project).and_return(project) - allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return(prometheus_service) - project.add_master(user) sign_in(user) end describe 'GET #active_common' do + before do + allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) + end + context 'when prometheus metrics are enabled' do context 'when data is not present' do before do - allow(prometheus_service).to receive(:matched_metrics).and_return({}) + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) end it 'returns no content response' do @@ -32,7 +33,7 @@ describe Projects::Prometheus::MetricsController do let(:sample_response) { { some_data: 1 } } before do - allow(prometheus_service).to receive(:matched_metrics).and_return(sample_response) + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response) end it 'returns no content response' do @@ -53,6 +54,18 @@ describe Projects::Prometheus::MetricsController do end end + describe '#prometheus_adapter' do + before do + allow(controller).to receive(:project).and_return(project) + end + + it 'calls prometheus adapter service' do + expect_any_instance_of(::Prometheus::AdapterService).to receive(:prometheus_adapter) + + subject.__send__(:prometheus_adapter) + end + end + def project_params(opts = {}) opts.reverse_merge(namespace_id: project.namespace, project_id: project) end diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index 1e125237ae8..6524bb4830c 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -1,6 +1,29 @@ FactoryBot.define do factory :board do - project + transient do + project nil + group nil + project_id nil + group_id nil + parent nil + end + + after(:build, :stub) do |board, evaluator| + if evaluator.group + board.group = evaluator.group + elsif evaluator.group_id + board.group_id = evaluator.group_id + elsif evaluator.project + board.project = evaluator.project + elsif evaluator.project_id + board.project_id = evaluator.project_id + elsif evaluator.parent + id = evaluator.parent.id + evaluator.parent.is_a?(Group) ? board.group_id = id : evaluator.project_id = id + else + board.project = create(:project, :empty_repo) + end + end after(:create) do |board| board.lists.create(list_type: :closed) diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 586c7b48d0b..986f864f0b5 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -37,6 +37,14 @@ feature 'Dashboard Projects' do expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']") end + + it 'shows the last_activity_at attribute as the update date' do + project.update_attributes!(last_repository_updated_at: 1.hour.ago, last_activity_at: Time.now) + + visit dashboard_projects_path + + expect(page).to have_xpath("//time[@datetime='#{project.last_activity_at.getutc.iso8601}']") + end end context 'when last_repository_updated_at and last_activity_at are missing' do diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 39bcea013e7..605298ba8ab 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do describe 'when checking branches' do context 'with artifacts' do before do - visit project_branches_path(project, search: 'binary-encoding') + visit project_branches_filtered_path(project, state: 'all', search: 'binary-encoding') end scenario 'shows download artifacts button' do diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 2fddd274078..2a9d9e6416c 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -11,15 +11,109 @@ describe 'Branches' do project.add_developer(user) end - describe 'Initial branches page' do - it 'shows all the branches sorted by last updated by default' do + context 'on the projects with 6 active branches and 4 stale branches' do + let(:project) { create(:project, :public, :empty_repo) } + let(:repository) { project.repository } + let(:threshold) { Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD } + + before do + # Add 4 stale branches + (1..4).reverse_each do |i| + Timecop.freeze((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") } + end + # Add 6 active branches + (1..6).each do |i| + Timecop.freeze((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") } + end + end + + describe 'Overview page of the branches' do + it 'shows the first 5 active branches and the first 4 stale branches sorted by last updated' do + visit project_branches_path(project) + + expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active')) + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + + expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active')) + expect(page).not_to have_content('Show more stale branches') + end + end + + describe 'Active branches page' do + it 'shows 6 active branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'active') + + expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active')) + end + end + + describe 'Stale branches page' do + it 'shows 4 active branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'stale') + + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + end + end + + describe 'All branches page' do + it 'shows 10 branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'all') + + expect(page).to have_content(sorted_branches(repository, count: 10, sort_by: :updated_desc)) + end + end + + context 'with branches over more than one page' do + before do + allow(Kaminari.config).to receive(:default_per_page).and_return(5) + end + + it 'shows only default_per_page active branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'active') + + expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc, state: 'active')) + end + + it 'shows only default_per_page branches sorted by last updated on All branches' do + visit project_branches_filtered_path(project, state: 'all') + + expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc)) + end + end + end + + describe 'Find branches' do + it 'shows filtered branches', :js do visit project_branches_path(project) + fill_in 'branch-search', with: 'fix' + find('#branch-search').native.send_keys(:enter) + + expect(page).to have_content('fix') + expect(find('.all-branches')).to have_selector('li', count: 1) + end + end + + describe 'Delete unprotected branch on Overview' do + it 'removes branch after confirmation', :js do + visit project_branches_filtered_path(project, state: 'all') + + expect(all('.all-branches').last).to have_selector('li', count: 20) + accept_confirm { find('.js-branch-add-pdf-text-binary .btn-remove').click } + + expect(all('.all-branches').last).to have_selector('li', count: 19) + end + end + + describe 'All branches page' do + it 'shows all the branches sorted by last updated by default' do + visit project_branches_filtered_path(project, state: 'all') + expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc)) end it 'sorts the branches by name' do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') click_button "Last updated" # Open sorting dropdown click_link "Name" @@ -28,7 +122,7 @@ describe 'Branches' do end it 'sorts the branches by oldest updated' do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') click_button "Last updated" # Open sorting dropdown click_link "Oldest updated" @@ -41,13 +135,13 @@ describe 'Branches' do %w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') } - expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count) + expect { visit project_branches_filtered_path(project, state: 'all') }.not_to exceed_query_limit(control_count) end end - describe 'Find branches' do + describe 'Find branches on All branches' do it 'shows filtered branches', :js do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') fill_in 'branch-search', with: 'fix' find('#branch-search').native.send_keys(:enter) @@ -57,9 +151,9 @@ describe 'Branches' do end end - describe 'Delete unprotected branch' do + describe 'Delete unprotected branch on All branches' do it 'removes branch after confirmation', :js do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') fill_in 'branch-search', with: 'fix' @@ -73,6 +167,19 @@ describe 'Branches' do expect(find('.all-branches')).to have_selector('li', count: 0) end end + + context 'on project with 0 branch' do + let(:project) { create(:project, :public, :empty_repo) } + let(:repository) { project.repository } + + describe '0 branches on Overview' do + it 'shows warning' do + visit project_branches_path(project) + + expect(page).not_to have_selector('.all-branches') + end + end + end end context 'logged in as master' do @@ -83,7 +190,7 @@ describe 'Branches' do describe 'Initial branches page' do it 'shows description for admin' do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') expect(page).to have_content("Protected branches can be managed in project settings") end @@ -102,12 +209,18 @@ describe 'Branches' do end end - def sorted_branches(repository, count:, sort_by:) + def sorted_branches(repository, count:, sort_by:, state: nil) + branches = repository.branches_sorted_by(sort_by) + branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state sorted_branches = - repository.branches_sorted_by(sort_by).first(count).map do |branch| + branches.first(count).map do |branch| Regexp.escape(branch.name) end Regexp.new(sorted_branches.join('.*')) end + + def create_file(message: 'message', branch_name:) + repository.create_file(user, generate(:branch), 'content', message: message, branch_name: branch_name) + end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 64e600144e0..b233af83eec 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -234,7 +234,7 @@ feature 'Environment' do end scenario 'user deletes the branch with running environment' do - visit project_branches_path(project, search: 'feature') + visit project_branches_filtered_path(project, state: 'all', search: 'feature') remove_branch_with_hooks(project, user, 'feature') do page.within('.js-branch-feature') { find('a.btn-remove').click } diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 85d518c0cc3..40689964b91 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -81,8 +81,8 @@ feature 'Merge Request button' do context 'on branches page' do it_behaves_like 'Merge request button only shown when allowed' do let(:label) { 'Merge request' } - let(:url) { project_branches_path(project, search: 'feature') } - let(:fork_url) { project_branches_path(forked_project, search: 'feature') } + let(:url) { project_branches_filtered_path(project, state: 'all', search: 'feature') } + let(:fork_url) { project_branches_filtered_path(forked_project, state: 'all', search: 'feature') } end end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 2a0d235ef04..233d2e67b9d 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -258,7 +258,7 @@ feature 'Pages' do end let(:ci_build) do - build( + create( :ci_build, project: project, pipeline: pipeline, diff --git a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json new file mode 100644 index 00000000000..d24a6f93f4b --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "allOf": [ + { "$ref": "identity.json" }, + { + "required": [ + "export_status" + ], + "properties": { + "export_status": { + "type": "string", + "enum": ["none", "started", "finished"] + } + } + } + ] +} diff --git a/spec/fixtures/api/schemas/public_api/v4/project/identity.json b/spec/fixtures/api/schemas/public_api/v4/project/identity.json new file mode 100644 index 00000000000..e35ab023d44 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/project/identity.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required": [ + "id", + "description", + "name", + "name_with_namespace", + "path", + "path_with_namespace", + "created_at" + ], + "properties": { + "id": { "type": "integer" }, + "description": { "type": ["string", "null"] }, + "name": { "type": "string" }, + "name_with_namespace": { "type": "string" }, + "path": { "type": "string" }, + "path_with_namespace": { "type": "string" }, + "created_at": { "type": "date" } + } +} diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb index a3c5ab99c87..b22947911b9 100644 --- a/spec/helpers/boards_helper_spec.rb +++ b/spec/helpers/boards_helper_spec.rb @@ -1,6 +1,34 @@ require 'spec_helper' describe BoardsHelper do + describe '#build_issue_link_base' do + context 'project board' do + it 'returns correct path for project board' do + @project = create(:project) + @board = create(:board, project: @project) + + expect(build_issue_link_base).to eq("/#{@project.namespace.path}/#{@project.path}/issues") + end + end + + context 'group board' do + let(:base_group) { create(:group, path: 'base') } + + it 'returns correct path for base group' do + @board = create(:board, group: base_group) + + expect(build_issue_link_base).to eq('/base/:project_path/issues') + end + + it 'returns correct path for subgroup' do + subgroup = create(:group, parent: base_group, path: 'sub') + @board = create(:board, group: subgroup) + + expect(build_issue_link_base).to eq('/base/sub/:project_path/issues') + end + end + end + describe '#board_data' do let(:user) { create(:user) } let(:project) { create(:project) } diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index cf3a76d0d2e..5477581c1b9 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -137,6 +137,27 @@ describe('Api', () => { done(); }); }); + + it('creates a group label', (done) => { + const namespace = 'group/subgroup'; + const labelData = { some: 'data' }; + const expectedUrl = `${dummyUrlRoot}/groups/${namespace}/-/labels`; + const expectedData = { + label: labelData, + }; + mock.onPost(expectedUrl).reply((config) => { + expect(config.data).toBe(JSON.stringify(expectedData)); + + return [200, { + name: 'test', + }]; + }); + + Api.newLabel(namespace, undefined, labelData, (response) => { + expect(response.name).toBe('test'); + done(); + }); + }); }); describe('groupProjects', () => { diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js index 5bb37304372..e4c3bf2bef1 100644 --- a/spec/javascripts/environments/environments_app_spec.js +++ b/spec/javascripts/environments/environments_app_spec.js @@ -61,6 +61,7 @@ describe('Environment', () => { }); describe('with paginated environments', () => { + let backupInterceptors; const environmentsResponseInterceptor = (request, next) => { next((response) => { response.headers.set('X-nExt-pAge', '2'); @@ -84,16 +85,16 @@ describe('Environment', () => { }; beforeEach(() => { - Vue.http.interceptors.push(environmentsResponseInterceptor); - Vue.http.interceptors.push(headersInterceptor); + backupInterceptors = Vue.http.interceptors; + Vue.http.interceptors = [ + environmentsResponseInterceptor, + headersInterceptor, + ]; component = mountComponent(EnvironmentsComponent, mockData); }); afterEach(() => { - Vue.http.interceptors = _.without( - Vue.http.interceptors, environmentsResponseInterceptor, - ); - Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor); + Vue.http.interceptors = backupInterceptors; }); it('should render a table with environments', (done) => { diff --git a/spec/javascripts/fixtures/environments.rb b/spec/javascripts/fixtures/environments.rb deleted file mode 100644 index d2457d75419..00000000000 --- a/spec/javascripts/fixtures/environments.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' - -describe Projects::EnvironmentsController, '(JavaScript fixtures)', type: :controller do - include JavaScriptFixturesHelpers - - let(:admin) { create(:admin) } - let(:namespace) { create(:namespace, name: 'frontend-fixtures' )} - let(:project) { create(:project_empty_repo, namespace: namespace, path: 'environments-project') } - let(:environment) { create(:environment, name: 'production', project: project) } - - render_views - - before(:all) do - clean_frontend_fixtures('environments/metrics') - end - - before do - sign_in(admin) - end - - it 'environments/metrics/metrics.html.raw' do |example| - get :metrics, - namespace_id: project.namespace, - project_id: project, - id: environment.id - - expect(response).to be_success - store_frontend_fixture(response, example.description) - end -end diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index eb8f6bbe50d..29b355307ef 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -5,24 +5,35 @@ import axios from '~/lib/utils/axios_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint } from './mock_data'; describe('Dashboard', () => { - const fixtureName = 'environments/metrics/metrics.html.raw'; let DashboardComponent; - let component; - preloadFixtures(fixtureName); + + const propsData = { + hasMetrics: false, + documentationPath: '/path/to/docs', + settingsPath: '/path/to/settings', + clustersPath: '/path/to/clusters', + tagsPath: '/path/to/tags', + projectPath: '/path/to/project', + metricsEndpoint: mockApiEndpoint, + deploymentEndpoint: null, + emptyGettingStartedSvgPath: '/path/to/getting-started.svg', + emptyLoadingSvgPath: '/path/to/loading.svg', + emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', + }; beforeEach(() => { - loadFixtures(fixtureName); + setFixtures('<div class="prometheus-graphs"></div>'); DashboardComponent = Vue.extend(Dashboard); }); describe('no metrics are available yet', () => { it('shows a getting started empty state when no metrics are present', () => { - component = new DashboardComponent({ - el: document.querySelector('#prometheus-graphs'), + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData, }); - component.$mount(); - expect(component.$el.querySelector('#prometheus-graphs')).toBe(null); + expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); expect(component.state).toEqual('gettingStarted'); }); }); @@ -30,11 +41,8 @@ describe('Dashboard', () => { describe('requests information to the server', () => { let mock; beforeEach(() => { - document.querySelector('#prometheus-graphs').setAttribute('data-has-metrics', 'true'); mock = new MockAdapter(axios); - mock.onGet(mockApiEndpoint).reply(200, { - metricsGroupsAPIResponse, - }); + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); }); afterEach(() => { @@ -42,14 +50,43 @@ describe('Dashboard', () => { }); it('shows up a loading state', (done) => { - component = new DashboardComponent({ - el: document.querySelector('#prometheus-graphs'), + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true }, }); - component.$mount(); + Vue.nextTick(() => { expect(component.state).toEqual('loading'); done(); }); }); + + it('hides the legend when showLegend is false', (done) => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true, showLegend: false }, + }); + + setTimeout(() => { + expect(component.showEmptyState).toEqual(false); + expect(component.$el.querySelector('.legend-group')).toEqual(null); + expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy(); + done(); + }); + }); + + it('hides the group panels when showPanels is false', (done) => { + const component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, hasMetrics: true, showPanels: false }, + }); + + setTimeout(() => { + expect(component.showEmptyState).toEqual(false); + expect(component.$el.querySelector('.prometheus-panel')).toEqual(null); + expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy(); + done(); + }); + }); }); }); diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb index f8188675013..1668d3bbaac 100644 --- a/spec/lib/gitlab/ci/charts_spec.rb +++ b/spec/lib/gitlab/ci/charts_spec.rb @@ -1,6 +1,51 @@ require 'spec_helper' describe Gitlab::Ci::Charts do + context "yearchart" do + let(:project) { create(:project) } + let(:chart) { Gitlab::Ci::Charts::YearChart.new(project) } + + subject { chart.to } + + it 'goes until the end of the current month (including the whole last day of the month)' do + is_expected.to eq(Date.today.end_of_month.end_of_day) + end + + it 'starts at the beginning of the current year' do + expect(chart.from).to eq(chart.to.years_ago(1).beginning_of_month.beginning_of_day) + end + end + + context "monthchart" do + let(:project) { create(:project) } + let(:chart) { Gitlab::Ci::Charts::MonthChart.new(project) } + + subject { chart.to } + + it 'includes the whole current day' do + is_expected.to eq(Date.today.end_of_day) + end + + it 'starts one month ago' do + expect(chart.from).to eq(1.month.ago.beginning_of_day) + end + end + + context "weekchart" do + let(:project) { create(:project) } + let(:chart) { Gitlab::Ci::Charts::WeekChart.new(project) } + + subject { chart.to } + + it 'includes the whole current day' do + is_expected.to eq(Date.today.end_of_day) + end + + it 'starts one week ago' do + expect(chart.from).to eq(1.week.ago.beginning_of_day) + end + end + context "pipeline_times" do let(:project) { create(:project) } let(:chart) { Gitlab::Ci::Charts::PipelineTime.new(project) } diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 91c9625ba06..448c6fb57dd 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -399,4 +399,140 @@ describe Gitlab::Ci::Trace do end end end + + describe '#archive!' do + subject { trace.archive! } + + shared_examples 'archive trace file' do + it do + expect { subject }.to change { Ci::JobArtifact.count }.by(1) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace.file.exists?).to be_truthy + expect(build.job_artifacts_trace.file.filename).to eq('job.log') + expect(File.exist?(src_path)).to be_falsy + expect(src_checksum) + .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest) + expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum) + end + end + + shared_examples 'source trace file stays intact' do |error:| + it do + expect { subject }.to raise_error(error) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace).to be_nil + expect(File.exist?(src_path)).to be_truthy + end + end + + shared_examples 'archive trace in database' do + it do + expect { subject }.to change { Ci::JobArtifact.count }.by(1) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace.file.exists?).to be_truthy + expect(build.job_artifacts_trace.file.filename).to eq('job.log') + expect(build.old_trace).to be_nil + expect(src_checksum) + .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).hexdigest) + expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum) + end + end + + shared_examples 'source trace in database stays intact' do |error:| + it do + expect { subject }.to raise_error(error) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace).to be_nil + expect(build.old_trace).to eq(trace_content) + end + end + + context 'when job does not have trace artifact' do + context 'when trace file stored in default path' do + let!(:build) { create(:ci_build, :success, :trace_live) } + let!(:src_path) { trace.read { |s| return s.path } } + let!(:src_checksum) { Digest::SHA256.file(src_path).hexdigest } + + it_behaves_like 'archive trace file' + + context 'when failed to create clone file' do + before do + allow(IO).to receive(:copy_stream).and_return(0) + end + + it_behaves_like 'source trace file stays intact', error: Gitlab::Ci::Trace::ArchiveError + end + + context 'when failed to create job artifact record' do + before do + allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) + allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) + .and_return(%w[Error Error]) + end + + it_behaves_like 'source trace file stays intact', error: ActiveRecord::RecordInvalid + end + end + + context 'when trace is stored in database' do + let(:build) { create(:ci_build, :success) } + let(:trace_content) { 'Sample trace' } + let!(:src_checksum) { Digest::SHA256.hexdigest(trace_content) } + + before do + build.update_column(:trace, trace_content) + end + + it_behaves_like 'archive trace in database' + + context 'when failed to create clone file' do + before do + allow(IO).to receive(:copy_stream).and_return(0) + end + + it_behaves_like 'source trace in database stays intact', error: Gitlab::Ci::Trace::ArchiveError + end + + context 'when failed to create job artifact record' do + before do + allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) + allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) + .and_return(%w[Error Error]) + end + + it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid + end + end + end + + context 'when job has trace artifact' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it 'does not archive' do + expect_any_instance_of(described_class).not_to receive(:archive_stream!) + expect { subject }.to raise_error('Already archived') + expect(build.job_artifacts_trace.file.exists?).to be_truthy + end + end + + context 'when job is not finished yet' do + let!(:build) { create(:ci_build, :running, :trace_live) } + + it 'does not archive' do + expect_any_instance_of(described_class).not_to receive(:archive_stream!) + expect { subject }.to raise_error('Job is not finished yet') + expect(build.trace.exist?).to be_truthy + end + end + end end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 708870060e7..a19155ed5b0 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -59,5 +59,69 @@ describe Gitlab::Git::Branch, seed_helper: true do it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) } end + context 'with active, stale and future branches' do + let(:repository) do + Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') + end + + let(:user) { create(:user) } + let(:committer) do + Gitlab::Git.committer_hash(email: user.email, name: user.name) + end + let(:params) do + parents = [repository.rugged.head.target] + tree = parents.first.tree + + { + message: 'commit message', + author: committer, + committer: committer, + tree: tree, + parents: parents + } + end + let(:stale_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } } + let(:active_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } } + let(:future_sha) { Timecop.freeze(100.days.since) { create_commit } } + + before do + repository.create_branch('stale-1', stale_sha) + repository.create_branch('active-1', active_sha) + repository.create_branch('future-1', future_sha) + end + + after do + ensure_seeds + end + + describe 'examine if the branch is active or stale' do + let(:stale_branch) { repository.find_branch('stale-1') } + let(:active_branch) { repository.find_branch('active-1') } + let(:future_branch) { repository.find_branch('future-1') } + + describe '#active?' do + it { expect(stale_branch.active?).to be_falsey } + it { expect(active_branch.active?).to be_truthy } + it { expect(future_branch.active?).to be_truthy } + end + + describe '#stale?' do + it { expect(stale_branch.stale?).to be_truthy } + it { expect(active_branch.stale?).to be_falsey } + it { expect(future_branch.stale?).to be_falsey } + end + + describe '#state' do + it { expect(stale_branch.state).to eq(:stale) } + it { expect(active_branch.state).to eq(:active) } + it { expect(future_branch.state).to eq(:active) } + end + end + end + it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } + + def create_commit + repository.create_commit(params.merge(committer: committer.merge(time: Time.now))) + end end diff --git a/spec/lib/gitlab/git/gitlab_projects_spec.rb b/spec/lib/gitlab/git/gitlab_projects_spec.rb index f4b964e1ee9..45bcd730332 100644 --- a/spec/lib/gitlab/git/gitlab_projects_spec.rb +++ b/spec/lib/gitlab/git/gitlab_projects_spec.rb @@ -61,10 +61,11 @@ describe Gitlab::Git::GitlabProjects do let(:remote_name) { 'remote-name' } let(:branch_name) { 'master' } let(:force) { false } + let(:prune) { true } let(:tags) { true } - let(:args) { { force: force, tags: tags }.merge(extra_args) } + let(:args) { { force: force, tags: tags, prune: prune }.merge(extra_args) } let(:extra_args) { {} } - let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --tags) } + let(:cmd) { %W(git fetch #{remote_name} --quiet --prune --tags) } subject { gl_projects.fetch_remote(remote_name, 600, args) } @@ -97,7 +98,7 @@ describe Gitlab::Git::GitlabProjects do context 'with --force' do let(:force) { true } - let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --force --tags) } + let(:cmd) { %W(git fetch #{remote_name} --quiet --prune --force --tags) } it 'executes the command with forced option' do stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) @@ -108,7 +109,18 @@ describe Gitlab::Git::GitlabProjects do context 'with --no-tags' do let(:tags) { false } - let(:cmd) { %W(git fetch #{remote_name} --prune --quiet --no-tags) } + let(:cmd) { %W(git fetch #{remote_name} --quiet --prune --no-tags) } + + it 'executes the command' do + stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) + + is_expected.to be_truthy + end + end + + context 'with no prune' do + let(:prune) { false } + let(:cmd) { %W(git fetch #{remote_name} --quiet --tags) } it 'executes the command' do stub_spawn(cmd, 600, tmp_repo_path, {}, success: true) diff --git a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb index c50e73cecfc..1c41dbcb9ef 100644 --- a/spec/lib/gitlab/gitaly_client/repository_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/repository_service_spec.rb @@ -85,6 +85,20 @@ describe Gitlab::GitalyClient::RepositoryService do end end + describe '#fetch_remote' do + let(:ssh_auth) { double(:ssh_auth, ssh_import?: true, ssh_key_auth?: false, ssh_known_hosts: nil) } + let(:import_url) { 'ssh://example.com' } + + it 'sends a fetch_remote_request message' do + expect_any_instance_of(Gitaly::RepositoryService::Stub) + .to receive(:fetch_remote) + .with(gitaly_request_with_params(no_prune: false), kind_of(Hash)) + .and_return(double(value: true)) + + client.fetch_remote(import_url, ssh_auth: ssh_auth, forced: false, no_tags: false, timeout: 60) + end + end + describe '#rebase_in_progress?' do let(:rebase_id) { 1 } diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index a93a921e459..4897d604bc1 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarRestorer do include UploadHelpers - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:shared) { project.import_export_shared } let(:project) { create(:project) } before do diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 3fb5ddde8b5..f40d4bc2d08 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarSaver do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:shared) { project.import_export_shared } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:project) { create(:project) } diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index 5cdc5138fda..58b9fb06cc5 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::FileImporter do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" } let(:valid_file) { "#{shared.export_path}/valid.json" } let(:symlink_file) { "#{shared.export_path}/invalid.json" } @@ -12,6 +12,7 @@ describe Gitlab::ImportExport::FileImporter do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) + allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') allow(SecureRandom).to receive(:hex).and_return('abcd') setup_files end diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index cfb15ee7e8b..17e06a6a83f 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -7,7 +7,7 @@ describe 'forked project import' do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } let(:forked_from_project) { create(:project, :repository) } let(:forked_project) { fork_project(project_with_repo, nil, repository: true) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 1a4d09724fc..f4e466d1296 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -7,9 +7,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do @user = create(:user) RSpec::Mocks.with_temporary_scope do - @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') - allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') + @shared = @project.import_export_shared + allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true) allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false) @@ -263,7 +263,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'Light JSON' do let(:user) { create(:user) } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } + let(:shared) { project.import_export_shared } let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index d6bd5f5c81d..3049491f0ae 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver do describe 'saves the project tree into a json object' do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb index e9f5273725d..1ef024d3078 100644 --- a/spec/lib/gitlab/import_export/reader_spec.rb +++ b/spec/lib/gitlab/import_export/reader_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::Reader do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } let(:test_config) { 'spec/support/import_export/import_export.yml' } let(:project_tree_hash) do { diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index c49af602a01..dc806d036ff 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::RepoRestorer do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index 44f972fe530..187ec8fcfa2 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::RepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } let(:bundler) { described_class.new(project: project, shared: shared) } before do diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index 8a3a244be21..acef97459b8 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::UploadsRestorer do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 177036c109b..1304d8fabfc 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::ImportExport::UploadsSaver do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb index e7d50f75682..49d857d9483 100644 --- a/spec/lib/gitlab/import_export/version_checker_spec.rb +++ b/spec/lib/gitlab/import_export/version_checker_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' include ImportExport::CommonUtil describe Gitlab::ImportExport::VersionChecker do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } describe 'bundle a project Git repo' do let(:version) { Gitlab::ImportExport.version } before do + allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('') allow(File).to receive(:open).and_return(version) end diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index 1d1e7e7f89a..d2bd8ccdf3f 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } let!(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb index 81b654e9c5f..5c01ee0ebb8 100644 --- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::WikiRestorer do let!(:project_without_wiki) { create(:project) } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { project.import_export_shared } let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb index 0697cb2def6..c7169717fc1 100644 --- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do include_examples 'additional metrics query' do let(:deployment) { create(:deployment, environment: environment) } - let(:query_params) { [environment.id, deployment.id] } + let(:query_params) { [deployment.id] } it 'queries using specific time' do expect(client).to receive(:query_range).with(anything, diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb index 84dc31d9732..ffe3ad85baa 100644 --- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Prometheus::Queries::DeploymentQuery do expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100', time: stop_time) - expect(subject.query(environment.id, deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil, - cpu_values: nil, cpu_before: nil, cpu_after: nil) + expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil, + cpu_values: nil, cpu_before: nil, cpu_after: nil) end end diff --git a/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb index c95719eff1d..420218a695a 100644 --- a/spec/lib/gitlab/prometheus/queries/matched_metrics_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Prometheus::Queries::MatchedMetricsQuery do +describe Gitlab::Prometheus::Queries::MatchedMetricQuery do include Prometheus::MetricBuilders let(:metric_group_class) { Gitlab::Prometheus::MetricGroup } diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 4506cbc3982..56b45d8da3c 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -508,8 +508,8 @@ describe Gitlab::Shell do end shared_examples 'fetch_remote' do |gitaly_on| - def fetch_remote(ssh_auth = nil) - gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth) + def fetch_remote(ssh_auth = nil, prune = true) + gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', ssh_auth: ssh_auth, prune: prune) end def expect_gitlab_projects(fail = false, options = {}) @@ -555,27 +555,33 @@ describe Gitlab::Shell do end it 'returns true when the command succeeds' do - expect_call(false, force: false, tags: true) + expect_call(false, force: false, tags: true, prune: true) expect(fetch_remote).to be_truthy end + it 'returns true when the command succeeds' do + expect_call(false, force: false, tags: true, prune: false) + + expect(fetch_remote(nil, false)).to be_truthy + end + it 'raises an exception when the command fails' do - expect_call(true, force: false, tags: true) + expect_call(true, force: false, tags: true, prune: true) expect { fetch_remote }.to raise_error(Gitlab::Shell::Error) end it 'allows forced and no_tags to be changed' do - expect_call(false, force: true, tags: false) + expect_call(false, force: true, tags: false, prune: true) - result = gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', forced: true, no_tags: true) + result = gitlab_shell.fetch_remote(repository.raw_repository, 'remote-name', forced: true, no_tags: true, prune: true) expect(result).to be_truthy end context 'SSH auth' do it 'passes the SSH key if specified' do - expect_call(false, force: false, tags: true, ssh_key: 'foo') + expect_call(false, force: false, tags: true, prune: true, ssh_key: 'foo') ssh_auth = build_ssh_auth(ssh_key_auth?: true, ssh_private_key: 'foo') @@ -583,7 +589,7 @@ describe Gitlab::Shell do end it 'does not pass an empty SSH key' do - expect_call(false, force: false, tags: true) + expect_call(false, force: false, tags: true, prune: true) ssh_auth = build_ssh_auth(ssh_key_auth: true, ssh_private_key: '') @@ -591,7 +597,7 @@ describe Gitlab::Shell do end it 'does not pass the key unless SSH key auth is to be used' do - expect_call(false, force: false, tags: true) + expect_call(false, force: false, tags: true, prune: true) ssh_auth = build_ssh_auth(ssh_key_auth: false, ssh_private_key: 'foo') @@ -599,7 +605,7 @@ describe Gitlab::Shell do end it 'passes the known_hosts data if specified' do - expect_call(false, force: false, tags: true, known_hosts: 'foo') + expect_call(false, force: false, tags: true, prune: true, known_hosts: 'foo') ssh_auth = build_ssh_auth(ssh_known_hosts: 'foo') @@ -607,7 +613,7 @@ describe Gitlab::Shell do end it 'does not pass empty known_hosts data' do - expect_call(false, force: false, tags: true) + expect_call(false, force: false, tags: true, prune: true) ssh_auth = build_ssh_auth(ssh_known_hosts: '') @@ -615,7 +621,7 @@ describe Gitlab::Shell do end it 'does not pass known_hosts data unless SSH is to be used' do - expect_call(false, force: false, tags: true) + expect_call(false, force: false, tags: true, prune: true) ssh_auth = build_ssh_auth(ssh_import?: false, ssh_known_hosts: 'foo') @@ -642,7 +648,7 @@ describe Gitlab::Shell do it 'passes the correct params to the gitaly service' do expect(repository.gitaly_repository_client).to receive(:fetch_remote) - .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, timeout: timeout) + .with(remote_name, ssh_auth: ssh_auth, forced: true, no_tags: true, prune: true, timeout: timeout) subject end diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb index bda239b7871..71a743495a2 100644 --- a/spec/lib/gitlab/utils_spec.rb +++ b/spec/lib/gitlab/utils_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Utils do - delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, to: :described_class + delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which, :ensure_array_from_string, to: :described_class describe '.slugify' do { @@ -83,4 +83,18 @@ describe Gitlab::Utils do expect(which('sh', 'PATH' => '/bin')).to eq('/bin/sh') end end + + describe '.ensure_array_from_string' do + it 'returns the same array if given one' do + arr = ['a', 4, true, { test: 1 }] + + expect(ensure_array_from_string(arr)).to eq(arr) + end + + it 'turns comma-separated strings into arrays' do + str = 'seven, eight, 9, 10' + + expect(ensure_array_from_string(str)).to eq(%w[seven eight 9 10]) + end + end end diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb new file mode 100644 index 00000000000..c18ae3b76d3 --- /dev/null +++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb') + +describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do + include Gitlab::Database::MigrationHelpers + + context 'when there are jobs in the queues' do + it 'correctly migrates queue when migrating up' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 0 + expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 2 + end + end + + it 'does not affect other queues under the same namespace' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1 + end + end + + it 'correctly migrates queue when migrating down' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + + described_class.new.down + + expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 1 + expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 0 + end + end + end + + context 'when there are no jobs in the queues' do + it 'does not raise error when migrating up' do + expect { described_class.new.up }.not_to raise_error + end + + it 'does not raise error when migrating down' do + expect { described_class.new.down }.not_to raise_error + end + end + + def stubbed_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end diff --git a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb new file mode 100644 index 00000000000..5e3b20ab4a8 --- /dev/null +++ b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180307012445_migrate_update_head_pipeline_for_merge_request_sidekiq_queue.rb') + +describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis do + include Gitlab::Database::MigrationHelpers + + context 'when there are jobs in the queues' do + it 'correctly migrates queue when migrating up' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 0 + expect(sidekiq_queue_length('pipeline_processing:update_head_pipeline_for_merge_request')).to eq 2 + end + end + + it 'does not affect other queues under the same namespace' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1 + end + end + + it 'correctly migrates queue when migrating down' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + + described_class.new.down + + expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1 + expect(sidekiq_queue_length('pipeline_processing:update_head_pipeline_for_merge_request')).to eq 0 + end + end + end + + context 'when there are no jobs in the queues' do + it 'does not raise error when migrating up' do + expect { described_class.new.up }.not_to raise_error + end + + it 'does not raise error when migrating down' do + expect { described_class.new.down }.not_to raise_error + end + end + + def stubbed_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index df8a508e021..2905b58066b 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -22,11 +22,11 @@ describe Clusters::Applications::Prometheus do end end - describe '#proxy_client' do + describe '#prometheus_client' do context 'cluster is nil' do it 'returns nil' do expect(subject.cluster).to be_nil - expect(subject.proxy_client).to be_nil + expect(subject.prometheus_client).to be_nil end end @@ -35,7 +35,7 @@ describe Clusters::Applications::Prometheus do subject { create(:clusters_applications_prometheus, cluster: cluster) } it 'returns nil' do - expect(subject.proxy_client).to be_nil + expect(subject.prometheus_client).to be_nil end end @@ -63,15 +63,15 @@ describe Clusters::Applications::Prometheus do end it 'creates proxy prometheus rest client' do - expect(subject.proxy_client).to be_instance_of(RestClient::Resource) + expect(subject.prometheus_client).to be_instance_of(RestClient::Resource) end it 'creates proper url' do - expect(subject.proxy_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80') + expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80') end it 'copies options and headers from kube client to proxy client' do - expect(subject.proxy_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) + expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers)) end end end diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb new file mode 100644 index 00000000000..f4b9c57e71a --- /dev/null +++ b/spec/models/concerns/prometheus_adapter_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe PrometheusAdapter, :use_clean_rails_memory_store_caching do + include PrometheusHelpers + include ReactiveCachingHelpers + + class TestClass + include PrometheusAdapter + end + + let(:project) { create(:prometheus_project) } + let(:service) { project.prometheus_service } + + let(:described_class) { TestClass } + let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery } + + describe '#query' do + describe 'environment' do + let(:environment) { build_stubbed(:environment, slug: 'env-slug') } + + around do |example| + Timecop.freeze { example.run } + end + + context 'with valid data' do + subject { service.query(:environment, environment) } + + before do + stub_reactive_cache(service, prometheus_data, environment_query, environment.id) + end + + it 'returns reactive data' do + is_expected.to eq(prometheus_metrics_data) + end + end + end + + describe 'matched_metrics' do + let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricQuery } + let(:prometheus_client_wrapper) { double(:prometheus_client_wrapper, label_values: nil) } + + context 'with valid data' do + subject { service.query(:matched_metrics) } + + before do + allow(service).to receive(:prometheus_client_wrapper).and_return(prometheus_client_wrapper) + synchronous_reactive_cache(service) + end + + it 'returns reactive data' do + expect(subject[:success]).to be_truthy + expect(subject[:data]).to eq([]) + end + end + end + + describe 'deployment' do + let(:deployment) { build_stubbed(:deployment) } + let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery } + + around do |example| + Timecop.freeze { example.run } + end + + context 'with valid data' do + subject { service.query(:deployment, deployment) } + + before do + stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id) + end + + it 'returns reactive data' do + expect(subject).to eq(prometheus_metrics_data) + end + end + end + end + + describe '#calculate_reactive_cache' do + let(:environment) { create(:environment, slug: 'env-slug') } + before do + service.manual_configuration = true + service.active = true + end + + subject do + service.calculate_reactive_cache(environment_query.name, environment.id) + end + + around do |example| + Timecop.freeze { example.run } + end + + context 'when service is inactive' do + before do + service.active = false + end + + it { is_expected.to be_nil } + end + + context 'when Prometheus responds with valid data' do + before do + stub_all_prometheus_requests(environment.slug) + end + + it { expect(subject.to_json).to eq(prometheus_data.to_json) } + it { expect(subject.to_json).to eq(prometheus_data.to_json) } + end + + [404, 500].each do |status| + context "when Prometheus responds with #{status}" do + before do + stub_all_prometheus_requests(environment.slug, status: status, body: "QUERY FAILED!") + end + + it { is_expected.to eq(success: false, result: %(#{status} - "QUERY FAILED!")) } + end + end + end +end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index ba8aa13d5ad..ac30cd27e0c 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -64,6 +64,7 @@ describe Deployment do describe '#metrics' do let(:deployment) { create(:deployment) } + let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } subject { deployment.metrics } @@ -76,17 +77,16 @@ describe Deployment do { success: true, metrics: {}, - last_update: 42, - deployment_time: 1494408956 + last_update: 42 } end before do - allow(deployment.project).to receive_message_chain(:monitoring_service, :deployment_metrics) - .with(any_args).and_return(simple_metrics) + allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:query).with(:deployment, deployment).and_return(simple_metrics) end - it { is_expected.to eq(simple_metrics) } + it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) } end end @@ -109,11 +109,11 @@ describe Deployment do } end - let(:prometheus_service) { double('prometheus_service') } + let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) } before do - allow(project).to receive(:prometheus_service).and_return(prometheus_service) - allow(prometheus_service).to receive(:additional_deployment_metrics).and_return(simple_metrics) + allow(deployment).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:query).with(:additional_metrics_deployment, deployment).and_return(simple_metrics) end it { is_expected.to eq(simple_metrics.merge({ deployment_time: deployment.created_at.to_i })) } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 6f24a039998..ceb570ac777 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Environment do - set(:project) { create(:project) } + let(:project) { create(:project) } subject(:environment) { create(:environment, project: project) } it { is_expected.to belong_to(:project) } @@ -452,8 +452,8 @@ describe Environment do end it 'returns the metrics from the deployment service' do - expect(project.monitoring_service) - .to receive(:environment_metrics).with(environment) + expect(environment.prometheus_adapter) + .to receive(:query).with(:environment, environment) .and_return(:fake_metrics) is_expected.to eq(:fake_metrics) @@ -508,12 +508,12 @@ describe Environment do context 'when the environment has additional metrics' do before do - allow(environment).to receive(:has_additional_metrics?).and_return(true) + allow(environment).to receive(:has_metrics?).and_return(true) end it 'returns the additional metrics from the deployment service' do - expect(project.prometheus_service).to receive(:additional_environment_metrics) - .with(environment) + expect(environment.prometheus_adapter).to receive(:query) + .with(:additional_metrics_environment, environment) .and_return(:fake_metrics) is_expected.to eq(:fake_metrics) @@ -522,46 +522,13 @@ describe Environment do context 'when the environment does not have metrics' do before do - allow(environment).to receive(:has_additional_metrics?).and_return(false) + allow(environment).to receive(:has_metrics?).and_return(false) end it { is_expected.to be_nil } end end - describe '#has_additional_metrics??' do - subject { environment.has_additional_metrics? } - - context 'when the enviroment is available' do - context 'with a deployment service' do - let(:project) { create(:prometheus_project) } - - context 'and a deployment' do - let!(:deployment) { create(:deployment, environment: environment) } - it { is_expected.to be_truthy } - end - - context 'but no deployments' do - it { is_expected.to be_falsy } - end - end - - context 'without a monitoring service' do - it { is_expected.to be_falsy } - end - end - - context 'when the environment is unavailable' do - let(:project) { create(:prometheus_project) } - - before do - environment.stop - end - - it { is_expected.to be_falsy } - end - end - describe '#slug' do it "is automatically generated" do expect(environment.slug).not_to be_nil @@ -654,4 +621,12 @@ describe Environment do end end end + + describe '#prometheus_adapter' do + it 'calls prometheus adapter service' do + expect_any_instance_of(Prometheus::AdapterService).to receive(:prometheus_adapter) + + subject.prometheus_adapter + end + end end diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index 6693e5783a5..7afb1b4a8e3 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -6,7 +6,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do let(:project) { create(:prometheus_project) } let(:service) { project.prometheus_service } - let(:environment_query) { Gitlab::Prometheus::Queries::EnvironmentQuery } describe "Associations" do it { is_expected.to belong_to :project } @@ -55,197 +54,31 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end end - describe '#environment_metrics' do - let(:environment) { build_stubbed(:environment, slug: 'env-slug') } - - around do |example| - Timecop.freeze { example.run } - end - - context 'with valid data' do - subject { service.environment_metrics(environment) } - - before do - stub_reactive_cache(service, prometheus_data, environment_query, environment.id) - end - - it 'returns reactive data' do - is_expected.to eq(prometheus_metrics_data) - end - end - end - - describe '#matched_metrics' do - let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricsQuery } - let(:client) { double(:client, label_values: nil) } - - context 'with valid data' do - subject { service.matched_metrics } - - before do - allow(service).to receive(:client).and_return(client) - synchronous_reactive_cache(service) - end - - it 'returns reactive data' do - expect(subject[:success]).to be_truthy - expect(subject[:data]).to eq([]) - end - end - end - - describe '#deployment_metrics' do - let(:deployment) { build_stubbed(:deployment) } - let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery } - - around do |example| - Timecop.freeze { example.run } - end - - context 'with valid data' do - subject { service.deployment_metrics(deployment) } - let(:fake_deployment_time) { 10 } - - before do - stub_reactive_cache(service, prometheus_data, deployment_query, deployment.environment.id, deployment.id) - end - - it 'returns reactive data' do - expect(deployment).to receive(:created_at).and_return(fake_deployment_time) - - expect(subject).to eq(prometheus_metrics_data.merge(deployment_time: fake_deployment_time)) - end - end - end - - describe '#calculate_reactive_cache' do - let(:environment) { create(:environment, slug: 'env-slug') } - before do - service.manual_configuration = true - service.active = true - end - - subject do - service.calculate_reactive_cache(environment_query.name, environment.id) - end - - around do |example| - Timecop.freeze { example.run } - end - - context 'when service is inactive' do - before do - service.active = false - end - - it { is_expected.to be_nil } - end - - context 'when Prometheus responds with valid data' do - before do - stub_all_prometheus_requests(environment.slug) - end - - it { expect(subject.to_json).to eq(prometheus_data.to_json) } - it { expect(subject.to_json).to eq(prometheus_data.to_json) } - end - - [404, 500].each do |status| - context "when Prometheus responds with #{status}" do - before do - stub_all_prometheus_requests(environment.slug, status: status, body: "QUERY FAILED!") - end - - it { is_expected.to eq(success: false, result: %(#{status} - "QUERY FAILED!")) } - end - end - end - - describe '#client' do + describe '#prometheus_client' do context 'manual configuration is enabled' do let(:api_url) { 'http://some_url' } + before do + subject.active = true subject.manual_configuration = true subject.api_url = api_url end - it 'returns simple rest client from api_url' do - expect(subject.client).to be_instance_of(Gitlab::PrometheusClient) - expect(subject.client.rest_client.url).to eq(api_url) + it 'returns rest client from api_url' do + expect(subject.prometheus_client.url).to eq(api_url) end end context 'manual configuration is disabled' do - let!(:cluster_for_all) { create(:cluster, environment_scope: '*', projects: [project]) } - let!(:cluster_for_dev) { create(:cluster, environment_scope: 'dev', projects: [project]) } - - let!(:prometheus_for_dev) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_dev) } - let(:proxy_client) { double('proxy_client') } + let(:api_url) { 'http://some_url' } before do - service.manual_configuration = false - end - - context 'with cluster for all environments with prometheus installed' do - let!(:prometheus_for_all) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_all) } - - context 'without environment supplied' do - it 'returns client handling all environments' do - expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice - - expect(service.client).to be_instance_of(Gitlab::PrometheusClient) - expect(service.client.rest_client).to eq(proxy_client) - end - end - - context 'with dev environment supplied' do - let!(:environment) { create(:environment, project: project, name: 'dev') } - - it 'returns dev cluster client' do - expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice - - expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient) - expect(service.client(environment.id).rest_client).to eq(proxy_client) - end - end - - context 'with prod environment supplied' do - let!(:environment) { create(:environment, project: project, name: 'prod') } - - it 'returns dev cluster client' do - expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice - - expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient) - expect(service.client(environment.id).rest_client).to eq(proxy_client) - end - end + subject.manual_configuration = false + subject.api_url = api_url end - context 'with cluster for all environments without prometheus installed' do - context 'without environment supplied' do - it 'raises PrometheusClient::Error because cluster was not found' do - expect { service.client }.to raise_error(Gitlab::PrometheusClient::Error, /couldn't find cluster with Prometheus installed/) - end - end - - context 'with dev environment supplied' do - let!(:environment) { create(:environment, project: project, name: 'dev') } - - it 'returns dev cluster client' do - expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice - - expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient) - expect(service.client(environment.id).rest_client).to eq(proxy_client) - end - end - - context 'with prod environment supplied' do - let!(:environment) { create(:environment, project: project, name: 'prod') } - - it 'raises PrometheusClient::Error because cluster was not found' do - expect { service.client }.to raise_error(Gitlab::PrometheusClient::Error, /couldn't find cluster with Prometheus installed/) - end - end + it 'no client provided' do + expect(subject.prometheus_client).to be_nil end end end @@ -284,7 +117,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end end - describe '#synchronize_service_state! before_save callback' do + describe '#synchronize_service_state before_save callback' do context 'no clusters with prometheus are installed' do context 'when service is inactive' do before do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 92ea8841123..b1c9e6754b9 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -518,6 +518,20 @@ describe Project do 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 + + it 'returns the most recent timestamp' do + project.update_attributes(updated_at: nil, + last_activity_at: timestamp, + last_repository_updated_at: timestamp - 1.hour) + + expect(project.last_activity_date).to eq(timestamp) + + project.update_attributes(updated_at: timestamp, + last_activity_at: timestamp - 1.hour, + last_repository_updated_at: nil) + + expect(project.last_activity_date).to eq(timestamp) + end end end diff --git a/spec/requests/api/group_boards_spec.rb b/spec/requests/api/group_boards_spec.rb new file mode 100644 index 00000000000..894c94688ba --- /dev/null +++ b/spec/requests/api/group_boards_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe API::GroupBoards do + set(:user) { create(:user) } + set(:non_member) { create(:user) } + set(:guest) { create(:user) } + set(:admin) { create(:user, :admin) } + set(:board_parent) { create(:group, :public) } + + before do + board_parent.add_owner(user) + end + + set(:project) { create(:project, :public, namespace: board_parent ) } + + set(:dev_label) do + create(:group_label, title: 'Development', color: '#FFAABB', group: board_parent) + end + + set(:test_label) do + create(:group_label, title: 'Testing', color: '#FFAACC', group: board_parent) + end + + set(:ux_label) do + create(:group_label, title: 'UX', color: '#FF0000', group: board_parent) + end + + set(:dev_list) do + create(:list, label: dev_label, position: 1) + end + + set(:test_list) do + create(:list, label: test_label, position: 2) + end + + set(:milestone) { create(:milestone, group: board_parent) } + set(:board_label) { create(:group_label, group: board_parent) } + + set(:board) { create(:board, group: board_parent, lists: [dev_list, test_list]) } + + it_behaves_like 'group and project boards', "/groups/:id/boards", false + + describe 'POST /groups/:id/boards/lists' do + let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" } + + it 'does not create lists for child project labels' do + project_label = create(:label, project: project) + + post api(url, user), label_id: project_label.id + + expect(response).to have_gitlab_http_status(400) + end + end +end diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb new file mode 100644 index 00000000000..fbed527963f --- /dev/null +++ b/spec/requests/api/project_export_spec.rb @@ -0,0 +1,290 @@ +require 'spec_helper' + +describe API::ProjectExport do + set(:project) { create(:project) } + set(:project_none) { create(:project) } + set(:project_started) { create(:project) } + set(:project_finished) { create(:project) } + set(:user) { create(:user) } + set(:admin) { create(:admin) } + + let(:path) { "/projects/#{project.id}/export" } + let(:path_none) { "/projects/#{project_none.id}/export" } + let(:path_started) { "/projects/#{project_started.id}/export" } + let(:path_finished) { "/projects/#{project_finished.id}/export" } + + let(:download_path) { "/projects/#{project.id}/export/download" } + let(:download_path_none) { "/projects/#{project_none.id}/export/download" } + let(:download_path_started) { "/projects/#{project_started.id}/export/download" } + let(:download_path_finished) { "/projects/#{project_finished.id}/export/download" } + + let(:export_path) { "#{Dir.tmpdir}/project_export_spec" } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + + # simulate exporting work directory + FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex') + + # simulate exported + FileUtils.mkdir_p project_finished.export_path + FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz') + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + shared_examples_for 'when project export is disabled' do + before do + stub_application_setting(project_export_enabled?: false) + end + + it_behaves_like '404 response' + end + + describe 'GET /projects/:project_id/export' do + shared_examples_for 'get project export status not found' do + it_behaves_like '404 response' do + let(:request) { get api(path, user) } + end + end + + shared_examples_for 'get project export status denied' do + it_behaves_like '403 response' do + let(:request) { get api(path, user) } + end + end + + shared_examples_for 'get project export status ok' do + it 'is none' do + get api(path_none, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/project/export_status') + expect(json_response['export_status']).to eq('none') + end + + it 'is started' do + get api(path_started, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/project/export_status') + expect(json_response['export_status']).to eq('started') + end + + it 'is finished' do + get api(path_finished, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/project/export_status') + expect(json_response['export_status']).to eq('finished') + end + end + + it_behaves_like 'when project export is disabled' do + let(:request) { get api(path, admin) } + end + + context 'when project export is enabled' do + context 'when user is an admin' do + let(:user) { admin } + + it_behaves_like 'get project export status ok' + end + + context 'when user is a master' do + before do + project.add_master(user) + project_none.add_master(user) + project_started.add_master(user) + project_finished.add_master(user) + end + + it_behaves_like 'get project export status ok' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like 'get project export status denied' + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'get project export status denied' + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'get project export status denied' + end + + context 'when user is not a member' do + it_behaves_like 'get project export status not found' + end + end + end + + describe 'GET /projects/:project_id/export/download' do + shared_examples_for 'get project export download not found' do + it_behaves_like '404 response' do + let(:request) { get api(download_path, user) } + end + end + + shared_examples_for 'get project export download denied' do + it_behaves_like '403 response' do + let(:request) { get api(download_path, user) } + end + end + + shared_examples_for 'get project export download' do + it_behaves_like '404 response' do + let(:request) { get api(download_path_none, user) } + end + + it_behaves_like '404 response' do + let(:request) { get api(download_path_started, user) } + end + + it 'downloads' do + get api(download_path_finished, user) + + expect(response).to have_gitlab_http_status(200) + end + end + + it_behaves_like 'when project export is disabled' do + let(:request) { get api(download_path, admin) } + end + + context 'when project export is enabled' do + context 'when user is an admin' do + let(:user) { admin } + + it_behaves_like 'get project export download' + end + + context 'when user is a master' do + before do + project.add_master(user) + project_none.add_master(user) + project_started.add_master(user) + project_finished.add_master(user) + end + + it_behaves_like 'get project export download' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like 'get project export download denied' + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'get project export download denied' + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'get project export download denied' + end + + context 'when user is not a member' do + it_behaves_like 'get project export download not found' + end + end + end + + describe 'POST /projects/:project_id/export' do + shared_examples_for 'post project export start not found' do + it_behaves_like '404 response' do + let(:request) { post api(path, user) } + end + end + + shared_examples_for 'post project export start denied' do + it_behaves_like '403 response' do + let(:request) { post api(path, user) } + end + end + + shared_examples_for 'post project export start' do + it 'starts' do + post api(path, user) + + expect(response).to have_gitlab_http_status(202) + end + end + + it_behaves_like 'when project export is disabled' do + let(:request) { post api(path, admin) } + end + + context 'when project export is enabled' do + context 'when user is an admin' do + let(:user) { admin } + + it_behaves_like 'post project export start' + end + + context 'when user is a master' do + before do + project.add_master(user) + project_none.add_master(user) + project_started.add_master(user) + project_finished.add_master(user) + end + + it_behaves_like 'post project export start' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like 'post project export start denied' + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'post project export start denied' + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'post project export start denied' + end + + context 'when user is not a member' do + it_behaves_like 'post project export start not found' + end + end + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index ce1311ac97c..95c23726a79 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -698,10 +698,10 @@ describe API::Runner do end end - context 'when tace is given' do + context 'when trace is given' do it 'creates a trace artifact' do allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do - CreateTraceArtifactWorker.new.perform(job.id) + ArchiveTraceWorker.new.perform(job.id) end update_job(state: 'success', trace: 'BUILD TRACE UPDATED') diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index db51a524e79..a715261cd6c 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -2,34 +2,20 @@ require 'spec_helper' describe Boards::CreateService do describe '#execute' do - let(:project) { create(:project) } + context 'when board parent is a project' do + let(:parent) { create(:project) } - subject(:service) { described_class.new(project, double) } + subject(:service) { described_class.new(parent, double) } - context 'when project does not have a board' do - it 'creates a new board' do - expect { service.execute }.to change(Board, :count).by(1) - end - - it 'creates the default lists' do - board = service.execute - - expect(board.lists.size).to eq 2 - expect(board.lists.first).to be_backlog - expect(board.lists.last).to be_closed - end + it_behaves_like 'boards create service' end - context 'when project has a board' do - before do - create(:board, project: project) - end + context 'when board parent is a group' do + let(:parent) { create(:group) } - it 'does not create a new board' do - expect(service).to receive(:can_create_board?) { false } + subject(:service) { described_class.new(parent, double) } - expect { service.execute }.not_to change(project.boards, :count) - end + it_behaves_like 'boards create service' end end end diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index ff5733b7064..b4efa3e44b6 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -2,98 +2,103 @@ require 'spec_helper' describe Boards::Issues::ListService do describe '#execute' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:board) { create(:board, project: project) } - - let(:bug) { create(:label, project: project, name: 'Bug') } - let(:development) { create(:label, project: project, name: 'Development') } - let(:testing) { create(:label, project: project, name: 'Testing') } - let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } - let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } - let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } - - let!(:backlog) { create(:backlog_list, board: board) } - let!(:list1) { create(:list, board: board, label: development, position: 0) } - let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:closed) { create(:closed_list, board: board) } - - let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) } - let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) } - let!(:reopened_issue1) { create(:issue, :opened, project: project) } - - let!(:list1_issue1) { create(:labeled_issue, project: project, labels: [p2, development]) } - let!(:list1_issue2) { create(:labeled_issue, project: project, labels: [development]) } - let!(:list1_issue3) { create(:labeled_issue, project: project, labels: [development, p1]) } - let!(:list2_issue1) { create(:labeled_issue, project: project, labels: [testing]) } - - let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) } - let!(:closed_issue3) { create(:issue, :closed, project: project) } - let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) } - let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) } - - before do - project.add_developer(user) - end - - it 'delegates search to IssuesFinder' do - params = { board_id: board.id, id: list1.id } - - expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original + context 'when parent is a project' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:board) { create(:board, project: project) } + + let(:m1) { create(:milestone, project: project) } + let(:m2) { create(:milestone, project: project) } + + let(:bug) { create(:label, project: project, name: 'Bug') } + let(:development) { create(:label, project: project, name: 'Development') } + let(:testing) { create(:label, project: project, name: 'Testing') } + let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } + let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } + let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } + + let!(:backlog) { create(:backlog_list, board: board) } + let!(:list1) { create(:list, board: board, label: development, position: 0) } + let!(:list2) { create(:list, board: board, label: testing, position: 1) } + let!(:closed) { create(:closed_list, board: board) } + + let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) } + let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2]) } + let!(:reopened_issue1) { create(:issue, :opened, project: project, title: 'Issue 3' ) } + + let!(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, development]) } + let!(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) } + let!(:list1_issue3) { create(:labeled_issue, project: project, milestone: m1, labels: [development, p1]) } + let!(:list2_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [testing]) } + + let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) } + let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3]) } + let!(:closed_issue3) { create(:issue, :closed, project: project) } + let!(:closed_issue4) { create(:labeled_issue, :closed, project: project, labels: [p1]) } + let!(:closed_issue5) { create(:labeled_issue, :closed, project: project, labels: [development]) } + + let(:parent) { project } + + before do + project.add_developer(user) + end - described_class.new(project, user, params).execute + it_behaves_like 'issues list service' end - context 'issues are ordered by priority' do - it 'returns opened issues when list_id is missing' do - params = { board_id: board.id } - - issues = described_class.new(project, user, params).execute - - expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] - end + context 'when parent is a group' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, :empty_repo, namespace: group) } + let(:project1) { create(:project, :empty_repo, namespace: group) } + let(:board) { create(:board, group: group) } - it 'returns opened issues when listing issues from Backlog' do - params = { board_id: board.id, id: backlog.id } + let(:m1) { create(:milestone, group: group) } + let(:m2) { create(:milestone, group: group) } - issues = described_class.new(project, user, params).execute + let(:bug) { create(:group_label, group: group, name: 'Bug') } + let(:development) { create(:group_label, group: group, name: 'Development') } + let(:testing) { create(:group_label, group: group, name: 'Testing') } - expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] - end + let(:p1) { create(:group_label, title: 'P1', group: group) } + let(:p2) { create(:group_label, title: 'P2', group: group) } + let(:p3) { create(:group_label, title: 'P3', group: group) } - it 'returns closed issues when listing issues from Closed' do - params = { board_id: board.id, id: closed.id } + let(:p1_project) { create(:label, title: 'P1_project', project: project, priority: 1) } + let(:p2_project) { create(:label, title: 'P2_project', project: project, priority: 2) } + let(:p3_project) { create(:label, title: 'P3_project', project: project, priority: 3) } - issues = described_class.new(project, user, params).execute + let(:p1_project1) { create(:label, title: 'P1_project1', project: project1, priority: 1) } + let(:p2_project1) { create(:label, title: 'P2_project1', project: project1, priority: 2) } + let(:p3_project1) { create(:label, title: 'P3_project1', project: project1, priority: 3) } - expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1] - end + let!(:backlog) { create(:backlog_list, board: board) } + let!(:list1) { create(:list, board: board, label: development, position: 0) } + let!(:list2) { create(:list, board: board, label: testing, position: 1) } + let!(:closed) { create(:closed_list, board: board) } - it 'returns opened issues that have label list applied when listing issues from a label list' do - params = { board_id: board.id, id: list1.id } + let!(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, title: 'Issue 1', labels: [bug]) } + let!(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, title: 'Issue 2', labels: [p2, p2_project]) } + let!(:reopened_issue1) { create(:issue, state: 'opened', project: project, title: 'Issue 3', closed_at: Time.now ) } - issues = described_class.new(project, user, params).execute + let!(:list1_issue1) { create(:labeled_issue, project: project, milestone: m1, labels: [p2, p2_project, development]) } + let!(:list1_issue2) { create(:labeled_issue, project: project, milestone: m2, labels: [development]) } + let!(:list1_issue3) { create(:labeled_issue, project: project1, milestone: m1, labels: [development, p1, p1_project1]) } + let!(:list2_issue1) { create(:labeled_issue, project: project1, milestone: m1, labels: [testing]) } - expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] - end - end + let!(:closed_issue1) { create(:labeled_issue, :closed, project: project, labels: [bug]) } + let!(:closed_issue2) { create(:labeled_issue, :closed, project: project, labels: [p3, p3_project]) } + let!(:closed_issue3) { create(:issue, :closed, project: project1) } + let!(:closed_issue4) { create(:labeled_issue, :closed, project: project1, labels: [p1, p1_project1]) } + let!(:closed_issue5) { create(:labeled_issue, :closed, project: project1, labels: [development]) } - context 'with list that does not belong to the board' do - it 'raises an error' do - list = create(:list) - service = described_class.new(project, user, board_id: board.id, id: list.id) + let(:parent) { group } - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + before do + group.add_developer(user) end - end - - context 'with invalid list id' do - it 'raises an error' do - service = described_class.new(project, user, board_id: board.id, id: nil) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + it_behaves_like 'issues list service' end end end diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 280e411683e..0a6b6d880d3 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -2,108 +2,53 @@ require 'spec_helper' describe Boards::Issues::MoveService do describe '#execute' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:board1) { create(:board, project: project) } - - let(:bug) { create(:label, project: project, name: 'Bug') } - let(:development) { create(:label, project: project, name: 'Development') } - let(:testing) { create(:label, project: project, name: 'Testing') } - - let!(:list1) { create(:list, board: board1, label: development, position: 0) } - let!(:list2) { create(:list, board: board1, label: testing, position: 1) } - let!(:closed) { create(:closed_list, board: board1) } - - before do - project.add_developer(user) - end - - context 'when moving an issue between lists' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } - - it 'delegates the label changes to Issues::UpdateService' do - expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once - - described_class.new(project, user, params).execute(issue) - end - - it 'removes the label from the list it came from and adds the label of the list it goes to' do - described_class.new(project, user, params).execute(issue) - - expect(issue.reload.labels).to contain_exactly(bug, testing) - end - end - - context 'when moving to closed' do + context 'when parent is a project' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:board1) { create(:board, project: project) } let(:board2) { create(:board, project: project) } + + let(:bug) { create(:label, project: project, name: 'Bug') } + let(:development) { create(:label, project: project, name: 'Development') } + let(:testing) { create(:label, project: project, name: 'Testing') } let(:regression) { create(:label, project: project, name: 'Regression') } - let!(:list3) { create(:list, board: board2, label: regression, position: 1) } - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } - let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } + let!(:list1) { create(:list, board: board1, label: development, position: 0) } + let!(:list2) { create(:list, board: board1, label: testing, position: 1) } + let!(:closed) { create(:closed_list, board: board1) } - it 'delegates the close proceedings to Issues::CloseService' do - expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once + let(:parent) { project } - described_class.new(project, user, params).execute(issue) + before do + parent.add_developer(user) end - it 'removes all list-labels from project boards and close the issue' do - described_class.new(project, user, params).execute(issue) - issue.reload - - expect(issue.labels).to contain_exactly(bug) - expect(issue).to be_closed - end + it_behaves_like 'issues move service' end - context 'when moving from closed' do - let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } - - it 'delegates the re-open proceedings to Issues::ReopenService' do - expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once - - described_class.new(project, user, params).execute(issue) - end - - it 'adds the label of the list it goes to and reopen the issue' do - described_class.new(project, user, params).execute(issue) - issue.reload - - expect(issue.labels).to contain_exactly(bug, testing) - expect(issue).to be_opened - end - end + context 'when parent is a group' do + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + let(:board1) { create(:board, group: group) } + let(:board2) { create(:board, group: group) } - context 'when moving to same list' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:bug) { create(:group_label, group: group, name: 'Bug') } + let(:development) { create(:group_label, group: group, name: 'Development') } + let(:testing) { create(:group_label, group: group, name: 'Testing') } + let(:regression) { create(:group_label, group: group, name: 'Regression') } - it 'returns false' do - expect(described_class.new(project, user, params).execute(issue)).to eq false - end + let!(:list1) { create(:list, board: board1, label: development, position: 0) } + let!(:list2) { create(:list, board: board1, label: testing, position: 1) } + let!(:closed) { create(:closed_list, board: board1) } - it 'keeps issues labels' do - described_class.new(project, user, params).execute(issue) + let(:parent) { group } - expect(issue.reload.labels).to contain_exactly(bug, development) + before do + parent.add_developer(user) end - it 'sorts issues' do - [issue, issue1, issue2].each do |issue| - issue.move_to_end && issue.save! - end - - params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) - - described_class.new(project, user, params).execute(issue) - - expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) - end + it_behaves_like 'issues move service' end end end diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb index 1d0be99fb35..7518e9e9b75 100644 --- a/spec/services/boards/list_service_spec.rb +++ b/spec/services/boards/list_service_spec.rb @@ -2,36 +2,20 @@ require 'spec_helper' describe Boards::ListService do describe '#execute' do - let(:project) { create(:project) } + context 'when board parent is a project' do + let(:parent) { create(:project) } - subject(:service) { described_class.new(project, double) } + subject(:service) { described_class.new(parent, double) } - context 'when project does not have a board' do - it 'creates a new project board' do - expect { service.execute }.to change(project.boards, :count).by(1) - end - - it 'delegates the project board creation to Boards::CreateService' do - expect_any_instance_of(Boards::CreateService).to receive(:execute).once - - service.execute - end + it_behaves_like 'boards list service' end - context 'when project has a board' do - before do - create(:board, project: project) - end - - it 'does not create a new board' do - expect { service.execute }.not_to change(project.boards, :count) - end - end + context 'when board parent is a group' do + let(:parent) { create(:group) } - it 'returns project boards' do - board = create(:board, project: project) + subject(:service) { described_class.new(parent, double) } - expect(service.execute).to match_array [board] + it_behaves_like 'boards list service' end end end diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index d5322e1bb21..7d3f5f86deb 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -2,62 +2,77 @@ require 'spec_helper' describe Boards::Lists::CreateService do describe '#execute' do - let(:project) { create(:project) } - let(:board) { create(:board, project: project) } - let(:user) { create(:user) } - let(:label) { create(:label, project: project, name: 'in-progress') } + shared_examples 'creating board lists' do + let(:user) { create(:user) } - subject(:service) { described_class.new(project, user, label_id: label.id) } + subject(:service) { described_class.new(parent, user, label_id: label.id) } - before do - project.add_developer(user) - end + before do + parent.add_developer(user) + end - context 'when board lists is empty' do - it 'creates a new list at beginning of the list' do - list = service.execute(board) + context 'when board lists is empty' do + it 'creates a new list at beginning of the list' do + list = service.execute(board) - expect(list.position).to eq 0 + expect(list.position).to eq 0 + end end - end - context 'when board lists has the done list' do - it 'creates a new list at beginning of the list' do - list = service.execute(board) + context 'when board lists has the done list' do + it 'creates a new list at beginning of the list' do + list = service.execute(board) - expect(list.position).to eq 0 + expect(list.position).to eq 0 + end end - end - context 'when board lists has labels lists' do - it 'creates a new list at end of the lists' do - create(:list, board: board, position: 0) - create(:list, board: board, position: 1) + context 'when board lists has labels lists' do + it 'creates a new list at end of the lists' do + create(:list, board: board, position: 0) + create(:list, board: board, position: 1) - list = service.execute(board) + list = service.execute(board) - expect(list.position).to eq 2 + expect(list.position).to eq 2 + end end - end - context 'when board lists has label and done lists' do - it 'creates a new list at end of the label lists' do - list1 = create(:list, board: board, position: 0) + context 'when board lists has label and done lists' do + it 'creates a new list at end of the label lists' do + list1 = create(:list, board: board, position: 0) - list2 = service.execute(board) + list2 = service.execute(board) - expect(list1.reload.position).to eq 0 - expect(list2.reload.position).to eq 1 + expect(list1.reload.position).to eq 0 + expect(list2.reload.position).to eq 1 + end end - end - context 'when provided label does not belongs to the project' do - it 'raises an error' do - label = create(:label, name: 'in-development') - service = described_class.new(project, user, label_id: label.id) + context 'when provided label does not belongs to the parent' do + it 'raises an error' do + label = create(:label, name: 'in-development') + service = described_class.new(parent, user, label_id: label.id) - expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound) + expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound) + end end end + + context 'when board parent is a project' do + let(:parent) { create(:project) } + let(:board) { create(:board, project: parent) } + let(:label) { create(:label, project: parent, name: 'in-progress') } + + it_behaves_like 'creating board lists' + end + + context 'when board parent is a group' do + let(:parent) { create(:group) } + let(:board) { create(:board, group: parent) } + let(:label) { create(:group_label, group: parent, name: 'in-progress') } + + it_behaves_like 'creating board lists' + end end end diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index bd98625b44f..3c4eb6b3fc5 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -2,37 +2,24 @@ require 'spec_helper' describe Boards::Lists::DestroyService do describe '#execute' do - let(:project) { create(:project) } - let(:board) { create(:board, project: project) } - let(:user) { create(:user) } + context 'when board parent is a project' do + let(:project) { create(:project) } + let(:board) { create(:board, project: project) } + let(:user) { create(:user) } - context 'when list type is label' do - it 'removes list from board' do - list = create(:list, board: board) - service = described_class.new(project, user) + let(:parent) { project } - expect { service.execute(list) }.to change(board.lists, :count).by(-1) - end - - it 'decrements position of higher lists' do - development = create(:list, board: board, position: 0) - review = create(:list, board: board, position: 1) - staging = create(:list, board: board, position: 2) - closed = board.closed_list - - described_class.new(project, user).execute(development) - - expect(review.reload.position).to eq 0 - expect(staging.reload.position).to eq 1 - expect(closed.reload.position).to be_nil - end + it_behaves_like 'lists destroy service' end - it 'does not remove list from board when list type is closed' do - list = board.closed_list - service = described_class.new(project, user) + context 'when board parent is a group' do + let(:group) { create(:group) } + let(:board) { create(:board, group: group) } + let(:user) { create(:user) } + + let(:parent) { group } - expect { service.execute(list) }.not_to change(board.lists, :count) + it_behaves_like 'lists destroy service' end end end diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index b189857e4f4..24e04eed642 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -1,33 +1,25 @@ require 'spec_helper' describe Boards::Lists::ListService do - let(:project) { create(:project) } - let(:board) { create(:board, project: project) } - let(:label) { create(:label, project: project) } - let!(:list) { create(:list, board: board, label: label) } - let(:service) { described_class.new(project, double) } - describe '#execute' do - context 'when the board has a backlog list' do - let!(:backlog_list) { create(:backlog_list, board: board) } - - it 'does not create a backlog list' do - expect { service.execute(board) }.not_to change(board.lists, :count) - end + context 'when board parent is a project' do + let(:project) { create(:project) } + let(:board) { create(:board, project: project) } + let(:label) { create(:label, project: project) } + let!(:list) { create(:list, board: board, label: label) } + let(:service) { described_class.new(project, double) } - it "returns board's lists" do - expect(service.execute(board)).to eq [backlog_list, list, board.closed_list] - end + it_behaves_like 'lists list service' end - context 'when the board does not have a backlog list' do - it 'creates a backlog list' do - expect { service.execute(board) }.to change(board.lists, :count).by(1) - end + context 'when board parent is a group' do + let(:group) { create(:group) } + let(:board) { create(:board, group: group) } + let(:label) { create(:group_label, group: group) } + let!(:list) { create(:list, board: board, label: label) } + let(:service) { described_class.new(group, double) } - it "returns board's lists" do - expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list] - end + it_behaves_like 'lists list service' end end end diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index a9d218bad49..16dfb2ae6af 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -2,100 +2,24 @@ require 'spec_helper' describe Boards::Lists::MoveService do describe '#execute' do - let(:project) { create(:project) } - let(:board) { create(:board, project: project) } - let(:user) { create(:user) } + context 'when board parent is a project' do + let(:project) { create(:project) } + let(:board) { create(:board, project: project) } + let(:user) { create(:user) } - let!(:planning) { create(:list, board: board, position: 0) } - let!(:development) { create(:list, board: board, position: 1) } - let!(:review) { create(:list, board: board, position: 2) } - let!(:staging) { create(:list, board: board, position: 3) } - let!(:closed) { create(:closed_list, board: board) } + let(:parent) { project } - context 'when list type is set to label' do - it 'keeps position of lists when new position is nil' do - service = described_class.new(project, user, position: nil) - - service.execute(planning) - - expect(current_list_positions).to eq [0, 1, 2, 3] - end - - it 'keeps position of lists when new positon is equal to old position' do - service = described_class.new(project, user, position: planning.position) - - service.execute(planning) - - expect(current_list_positions).to eq [0, 1, 2, 3] - end - - it 'keeps position of lists when new positon is negative' do - service = described_class.new(project, user, position: -1) - - service.execute(planning) - - expect(current_list_positions).to eq [0, 1, 2, 3] - end - - it 'keeps position of lists when new positon is equal to number of labels lists' do - service = described_class.new(project, user, position: board.lists.label.size) - - service.execute(planning) - - expect(current_list_positions).to eq [0, 1, 2, 3] - end - - it 'keeps position of lists when new positon is greater than number of labels lists' do - service = described_class.new(project, user, position: board.lists.label.size + 1) - - service.execute(planning) - - expect(current_list_positions).to eq [0, 1, 2, 3] - end - - it 'increments position of intermediate lists when new positon is equal to first position' do - service = described_class.new(project, user, position: 0) - - service.execute(staging) - - expect(current_list_positions).to eq [1, 2, 3, 0] - end - - it 'decrements position of intermediate lists when new positon is equal to last position' do - service = described_class.new(project, user, position: board.lists.label.last.position) - - service.execute(planning) - - expect(current_list_positions).to eq [3, 0, 1, 2] - end - - it 'decrements position of intermediate lists when new position is greater than old position' do - service = described_class.new(project, user, position: 2) - - service.execute(planning) - - expect(current_list_positions).to eq [2, 0, 1, 3] - end - - it 'increments position of intermediate lists when new position is lower than old position' do - service = described_class.new(project, user, position: 1) - - service.execute(staging) - - expect(current_list_positions).to eq [0, 2, 3, 1] - end + it_behaves_like 'lists move service' end - it 'keeps position of lists when list type is closed' do - service = described_class.new(project, user, position: 2) + context 'when board parent is a group' do + let(:group) { create(:group) } + let(:board) { create(:board, group: group) } + let(:user) { create(:user) } - service.execute(closed) + let(:parent) { group } - expect(current_list_positions).to eq [0, 1, 2, 3] + it_behaves_like 'lists move service' end end - - def current_list_positions - [planning, development, review, staging].map { |list| list.reload.position } - end end diff --git a/spec/services/ci/create_trace_artifact_service_spec.rb b/spec/services/ci/create_trace_artifact_service_spec.rb deleted file mode 100644 index 8c5e8e438c7..00000000000 --- a/spec/services/ci/create_trace_artifact_service_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'spec_helper' - -describe Ci::CreateTraceArtifactService do - describe '#execute' do - subject { described_class.new(nil, nil).execute(job) } - - context 'when the job does not have trace artifact' do - context 'when the job has a trace file' do - let!(:job) { create(:ci_build, :trace_live) } - let!(:legacy_path) { job.trace.read { |stream| return stream.path } } - let!(:legacy_checksum) { Digest::SHA256.file(legacy_path).hexdigest } - let(:new_path) { job.job_artifacts_trace.file.path } - let(:new_checksum) { Digest::SHA256.file(new_path).hexdigest } - - it { expect(File.exist?(legacy_path)).to be_truthy } - - it 'creates trace artifact' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) - - expect(File.exist?(legacy_path)).to be_falsy - expect(File.exist?(new_path)).to be_truthy - expect(new_checksum).to eq(legacy_checksum) - expect(job.job_artifacts_trace.file.exists?).to be_truthy - expect(job.job_artifacts_trace.file.filename).to eq('job.log') - end - - context 'when failed to create trace artifact record' do - before do - # When ActiveRecord error happens - allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) - allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) - .and_return("Error") - - subject rescue nil - - job.reload - end - - it 'keeps legacy trace and removes trace artifact' do - expect(File.exist?(legacy_path)).to be_truthy - expect(job.job_artifacts_trace).to be_nil - end - end - end - - context 'when the job does not have a trace file' do - let!(:job) { create(:ci_build) } - - it 'does not create trace artifact' do - expect { subject }.not_to change { Ci::JobArtifact.count } - end - end - end - - context 'when the job has already had trace artifact' do - let!(:job) { create(:ci_build, :trace_artifact) } - - it 'does not create trace artifact' do - expect { subject }.not_to change { Ci::JobArtifact.count } - end - end - end -end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index bfb86284d86..934106627a9 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -34,6 +34,7 @@ describe Projects::UpdatePagesService do context 'with expiry date' do before do build.artifacts_expire_in = "2 days" + build.save! end it "doesn't delete artifacts" do @@ -105,6 +106,7 @@ describe Projects::UpdatePagesService do context 'with expiry date' do before do build.artifacts_expire_in = "2 days" + build.save! end it "doesn't delete artifacts" do @@ -159,6 +161,20 @@ describe Projects::UpdatePagesService do expect(execute).not_to eq(:success) end + + context 'when timeout happens by DNS error' do + before do + allow_any_instance_of(described_class) + .to receive(:extract_zip_archive!).and_raise(SocketError) + end + + it 'raises an error' do + expect { execute }.to raise_error(SocketError) + + build.reload + expect(build.artifacts?).to eq(true) + end + end end end diff --git a/spec/services/prometheus/adapter_service_spec.rb b/spec/services/prometheus/adapter_service_spec.rb new file mode 100644 index 00000000000..335fc5844aa --- /dev/null +++ b/spec/services/prometheus/adapter_service_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe Prometheus::AdapterService do + let(:project) { create(:project) } + subject { described_class.new(project) } + + describe '#prometheus_adapter' do + let(:cluster) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [project]) } + + context 'prometheus service can execute queries' do + let(:prometheus_service) { double(:prometheus_service, can_query?: true) } + + before do + allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service + end + + it 'return prometheus service as prometheus adapter' do + expect(subject.prometheus_adapter).to eq(prometheus_service) + end + end + + context "prometheus service can't execute queries" do + let(:prometheus_service) { double(:prometheus_service, can_query?: false) } + + context 'with cluster with prometheus installed' do + let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } + + it 'returns application handling all environments' do + expect(subject.prometheus_adapter).to eq(prometheus) + end + end + + context 'with cluster without prometheus installed' do + it 'returns nil' do + expect(subject.prometheus_adapter).to be_nil + end + end + end + end +end diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb new file mode 100644 index 00000000000..5bdc04f660f --- /dev/null +++ b/spec/support/shared_examples/services/boards/boards_create_service.rb @@ -0,0 +1,27 @@ +shared_examples 'boards create service' do + context 'when parent does not have a board' do + it 'creates a new board' do + expect { service.execute }.to change(Board, :count).by(1) + end + + it 'creates the default lists' do + board = service.execute + + expect(board.lists.size).to eq 2 + expect(board.lists.first).to be_backlog + expect(board.lists.last).to be_closed + end + end + + context 'when parent has a board' do + before do + create(:board, parent: parent) + end + + it 'does not create a new board' do + expect(service).to receive(:can_create_board?) { false } + + expect { service.execute }.not_to change(parent.boards, :count) + end + end +end diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb new file mode 100644 index 00000000000..e0d5a7c61f2 --- /dev/null +++ b/spec/support/shared_examples/services/boards/boards_list_service.rb @@ -0,0 +1,29 @@ +shared_examples 'boards list service' do + context 'when parent does not have a board' do + it 'creates a new parent board' do + expect { service.execute }.to change(parent.boards, :count).by(1) + end + + it 'delegates the parent board creation to Boards::CreateService' do + expect_any_instance_of(Boards::CreateService).to receive(:execute).once + + service.execute + end + end + + context 'when parent has a board' do + before do + create(:board, parent: parent) + end + + it 'does not create a new board' do + expect { service.execute }.not_to change(parent.boards, :count) + end + end + + it 'returns parent boards' do + board = create(:board, parent: parent) + + expect(service.execute).to eq [board] + end +end diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb new file mode 100644 index 00000000000..3e744323cea --- /dev/null +++ b/spec/support/shared_examples/services/boards/issues_list_service.rb @@ -0,0 +1,60 @@ +shared_examples 'issues list service' do + it 'delegates search to IssuesFinder' do + params = { board_id: board.id, id: list1.id } + + expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original + + described_class.new(parent, user, params).execute + end + + context 'issues are ordered by priority' do + it 'returns opened issues when list_id is missing' do + params = { board_id: board.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] + end + + it 'returns opened issues when listing issues from Backlog' do + params = { board_id: board.id, id: backlog.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [opened_issue2, reopened_issue1, opened_issue1] + end + + it 'returns closed issues when listing issues from Closed' do + params = { board_id: board.id, id: closed.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [closed_issue4, closed_issue2, closed_issue5, closed_issue3, closed_issue1] + end + + it 'returns opened issues that have label list applied when listing issues from a label list' do + params = { board_id: board.id, id: list1.id } + + issues = described_class.new(parent, user, params).execute + + expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] + end + end + + context 'with list that does not belong to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(parent, user, board_id: board.id, id: list.id) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(parent, user, board_id: board.id, id: nil) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end +end diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb new file mode 100644 index 00000000000..4a4fbaa3a0e --- /dev/null +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -0,0 +1,87 @@ +shared_examples 'issues move service' do + context 'when moving an issue between lists' do + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } + + it 'delegates the label changes to Issues::UpdateService' do + expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once + + described_class.new(parent, user, params).execute(issue) + end + + it 'removes the label from the list it came from and adds the label of the list it goes to' do + described_class.new(parent, user, params).execute(issue) + + expect(issue.reload.labels).to contain_exactly(bug, testing) + end + end + + context 'when moving to closed' do + let!(:list3) { create(:list, board: board2, label: regression, position: 1) } + + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } } + + it 'delegates the close proceedings to Issues::CloseService' do + expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once + + described_class.new(parent, user, params).execute(issue) + end + + it 'removes all list-labels from boards and close the issue' do + described_class.new(parent, user, params).execute(issue) + issue.reload + + expect(issue.labels).to contain_exactly(bug) + expect(issue).to be_closed + end + end + + context 'when moving from closed' do + let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } + let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } } + + it 'delegates the re-open proceedings to Issues::ReopenService' do + expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once + + described_class.new(parent, user, params).execute(issue) + end + + it 'adds the label of the list it goes to and reopen the issue' do + described_class.new(parent, user, params).execute(issue) + issue.reload + + expect(issue.labels).to contain_exactly(bug, testing) + expect(issue).to be_opened + end + end + + context 'when moving to same list' do + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } + + it 'returns false' do + expect(described_class.new(parent, user, params).execute(issue)).to eq false + end + + it 'keeps issues labels' do + described_class.new(parent, user, params).execute(issue) + + expect(issue.reload.labels).to contain_exactly(bug, development) + end + + it 'sorts issues' do + [issue, issue1, issue2].each do |issue| + issue.move_to_end && issue.save! + end + + params.merge!(move_after_id: issue1.id, move_before_id: issue2.id) + + described_class.new(parent, user, params).execute(issue) + + expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) + end + end +end diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb new file mode 100644 index 00000000000..62b6ffe1836 --- /dev/null +++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb @@ -0,0 +1,30 @@ +shared_examples 'lists destroy service' do + context 'when list type is label' do + it 'removes list from board' do + list = create(:list, board: board) + service = described_class.new(parent, user) + + expect { service.execute(list) }.to change(board.lists, :count).by(-1) + end + + it 'decrements position of higher lists' do + development = create(:list, board: board, position: 0) + review = create(:list, board: board, position: 1) + staging = create(:list, board: board, position: 2) + closed = board.closed_list + + described_class.new(parent, user).execute(development) + + expect(review.reload.position).to eq 0 + expect(staging.reload.position).to eq 1 + expect(closed.reload.position).to be_nil + end + end + + it 'does not remove list from board when list type is closed' do + list = board.closed_list + service = described_class.new(parent, user) + + expect { service.execute(list) }.not_to change(board.lists, :count) + end +end diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb new file mode 100644 index 00000000000..0a8220111ab --- /dev/null +++ b/spec/support/shared_examples/services/boards/lists_list_service.rb @@ -0,0 +1,23 @@ +shared_examples 'lists list service' do + context 'when the board has a backlog list' do + let!(:backlog_list) { create(:backlog_list, board: board) } + + it 'does not create a backlog list' do + expect { service.execute(board) }.not_to change(board.lists, :count) + end + + it "returns board's lists" do + expect(service.execute(board)).to eq [backlog_list, list, board.closed_list] + end + end + + context 'when the board does not have a backlog list' do + it 'creates a backlog list' do + expect { service.execute(board) }.to change(board.lists, :count).by(1) + end + + it "returns board's lists" do + expect(service.execute(board)).to eq [board.backlog_list, list, board.closed_list] + end + end +end diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb new file mode 100644 index 00000000000..07c98cb29b7 --- /dev/null +++ b/spec/support/shared_examples/services/boards/lists_move_service.rb @@ -0,0 +1,93 @@ +shared_examples 'lists move service' do + let!(:planning) { create(:list, board: board, position: 0) } + let!(:development) { create(:list, board: board, position: 1) } + let!(:review) { create(:list, board: board, position: 2) } + let!(:staging) { create(:list, board: board, position: 3) } + let!(:closed) { create(:closed_list, board: board) } + + context 'when list type is set to label' do + it 'keeps position of lists when new position is nil' do + service = described_class.new(parent, user, position: nil) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is equal to old position' do + service = described_class.new(parent, user, position: planning.position) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is negative' do + service = described_class.new(parent, user, position: -1) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is equal to number of labels lists' do + service = described_class.new(parent, user, position: board.lists.label.size) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'keeps position of lists when new positon is greater than number of labels lists' do + service = described_class.new(parent, user, position: board.lists.label.size + 1) + + service.execute(planning) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + it 'increments position of intermediate lists when new positon is equal to first position' do + service = described_class.new(parent, user, position: 0) + + service.execute(staging) + + expect(current_list_positions).to eq [1, 2, 3, 0] + end + + it 'decrements position of intermediate lists when new positon is equal to last position' do + service = described_class.new(parent, user, position: board.lists.label.last.position) + + service.execute(planning) + + expect(current_list_positions).to eq [3, 0, 1, 2] + end + + it 'decrements position of intermediate lists when new position is greater than old position' do + service = described_class.new(parent, user, position: 2) + + service.execute(planning) + + expect(current_list_positions).to eq [2, 0, 1, 3] + end + + it 'increments position of intermediate lists when new position is lower than old position' do + service = described_class.new(parent, user, position: 1) + + service.execute(staging) + + expect(current_list_positions).to eq [0, 2, 3, 1] + end + end + + it 'keeps position of lists when list type is closed' do + service = described_class.new(parent, user, position: 2) + + service.execute(closed) + + expect(current_list_positions).to eq [0, 1, 2, 3] + end + + def current_list_positions + [planning, development, review, staging].map { |list| list.reload.position } + end +end diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb new file mode 100644 index 00000000000..bd18e8ffc1e --- /dev/null +++ b/spec/tasks/gitlab/traces_rake_spec.rb @@ -0,0 +1,55 @@ +require 'rake_helper' + +describe 'gitlab:traces rake tasks' do + before do + Rake.application.rake_require 'tasks/gitlab/traces' + end + + shared_examples 'passes the job id to worker' do + it do + expect(ArchiveTraceWorker).to receive(:bulk_perform_async).with([[job.id]]) + + run_rake_task('gitlab:traces:archive') + end + end + + shared_examples 'does not pass the job id to worker' do + it do + expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async) + + run_rake_task('gitlab:traces:archive') + end + end + + context 'when trace file stored in default path' do + let!(:job) { create(:ci_build, :success, :trace_live) } + + it_behaves_like 'passes the job id to worker' + end + + context 'when trace is stored in database' do + let!(:job) { create(:ci_build, :success) } + + before do + job.update_column(:trace, 'trace in db') + end + + it_behaves_like 'passes the job id to worker' + end + + context 'when job has trace artifact' do + let!(:job) { create(:ci_build, :success) } + + before do + create(:ci_job_artifact, :trace, job: job) + end + + it_behaves_like 'does not pass the job id to worker' + end + + context 'when job is not finished yet' do + let!(:build) { create(:ci_build, :running, :trace_live) } + + it_behaves_like 'does not pass the job id to worker' + end +end diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index c5f455b8948..f28bf430f02 100644 --- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -12,7 +12,7 @@ describe 'layouts/nav/sidebar/_project' do end describe 'issue boards' do - it 'has boards tab when multiple issue boards available' do + it 'has board tab' do render expect(rendered).to have_css('a[title="Board"]') diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb index 854abd9cca7..b768588c6e1 100644 --- a/spec/workers/create_trace_artifact_worker_spec.rb +++ b/spec/workers/archive_trace_worker_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CreateTraceArtifactWorker do +describe ArchiveTraceWorker do describe '#perform' do subject { described_class.new.perform(job&.id) } @@ -8,8 +8,7 @@ describe CreateTraceArtifactWorker do let(:job) { create(:ci_build) } it 'executes service' do - expect_any_instance_of(Ci::CreateTraceArtifactService) - .to receive(:execute).with(job) + expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) subject end @@ -19,8 +18,7 @@ describe CreateTraceArtifactWorker do let(:job) { nil } it 'does not execute service' do - expect_any_instance_of(Ci::CreateTraceArtifactService) - .not_to receive(:execute) + expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!) subject end diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index c7ff8cf3b92..acd8da11d8d 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -14,7 +14,7 @@ describe BuildFinishedWorker do expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform) expect_any_instance_of(BuildCoverageWorker).to receive(:perform) expect(BuildHooksWorker).to receive(:perform_async) - expect(CreateTraceArtifactWorker).to receive(:perform_async) + expect(ArchiveTraceWorker).to receive(:perform_async) described_class.new.perform(build.id) end diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb new file mode 100644 index 00000000000..24c0a3c6a20 --- /dev/null +++ b/spec/workers/concerns/pipeline_background_queue_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe PipelineBackgroundQueue do + let(:worker) do + Class.new do + def self.name + 'DummyWorker' + end + + include ApplicationWorker + include PipelineBackgroundQueue + end + end + + it 'sets a default object storage queue automatically' do + expect(worker.sidekiq_options['queue']) + .to eq 'pipeline_background:dummy' + end +end |