diff options
Diffstat (limited to 'spec')
35 files changed, 829 insertions, 74 deletions
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 964246b580c..37a253fde9b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -345,7 +345,8 @@ describe Projects::MergeRequestsController do end before do - create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) + pipeline = create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) + merge_request.update(head_pipeline: pipeline) end it 'returns :merge_when_pipeline_succeeds' do @@ -1159,7 +1160,10 @@ describe Projects::MergeRequestsController do let(:status) { pipeline.detailed_status(double('user')) } - before { get_pipeline_status } + before do + merge_request.update(head_pipeline: pipeline) + get_pipeline_status + end it 'return a detailed head_pipeline status in json' do expect(response).to have_http_status(:ok) diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index df2714f91ff..cbeb73d9cae 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -12,8 +12,10 @@ feature 'Cycle Analytics', feature: true, js: true do context 'as an allowed user' do context 'when project is new' do before do - project.team << [user, :master] + project.add_master(user) + login_as(user) + visit namespace_project_cycle_analytics_path(project.namespace, project) wait_for_ajax end @@ -30,7 +32,9 @@ feature 'Cycle Analytics', feature: true, js: true do context "when there's cycle analytics data" do before do - project.team << [user, :master] + allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) + mr.update(head_pipeline: pipeline) + project.add_master(user) create_cycle deploy_master @@ -84,7 +88,7 @@ feature 'Cycle Analytics', feature: true, js: true do context "as a guest" do before do - project.team << [guest, :guest] + project.add_guest(guest) allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) create_cycle diff --git a/spec/features/groups/members/sorting_spec.rb b/spec/features/groups/members/sorting_spec.rb index 608aedd3471..902d3f789ff 100644 --- a/spec/features/groups/members/sorting_spec.rb +++ b/spec/features/groups/members/sorting_spec.rb @@ -68,7 +68,7 @@ feature 'Groups > Members > Sorting', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end - scenario 'sorts by recent sign in' do + scenario 'sorts by recent sign in', :redis do visit_members_list(sort: :recent_sign_in) expect(first_member).to include(owner.name) @@ -76,7 +76,7 @@ feature 'Groups > Members > Sorting', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end - scenario 'sorts by oldest sign in' do + scenario 'sorts by oldest sign in', :redis do visit_members_list(sort: :oldest_sign_in) expect(first_member).to include(developer.name) diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb index f3ec80bb149..414838fa22e 100644 --- a/spec/features/issuables/issuable_list_spec.rb +++ b/spec/features/issuables/issuable_list_spec.rb @@ -52,6 +52,9 @@ describe 'issuable list', feature: true do create(:issue, project: project, author: user) else create(:merge_request, source_project: project, source_branch: generate(:branch)) + source_branch = FFaker::Name.name + pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any') + create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline) end 2.times do diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 11d417c253d..c82e8c03343 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -41,7 +41,7 @@ feature 'Login', feature: true do expect(page).to have_content('Your account has been blocked.') end - it 'does not update Devise trackable attributes' do + it 'does not update Devise trackable attributes', :redis do user = create(:user, :blocked) expect { login_with(user) }.not_to change { user.reload.sign_in_count } @@ -55,7 +55,7 @@ feature 'Login', feature: true do expect(page).to have_content('Invalid Login or password.') end - it 'does not update Devise trackable attributes' do + it 'does not update Devise trackable attributes', :redis do expect { login_with(User.ghost) }.not_to change { User.ghost.reload.sign_in_count } end end diff --git a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb index b79667a1a4c..5820784f8e7 100644 --- a/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb +++ b/spec/features/merge_requests/merge_immediately_with_pipeline_spec.rb @@ -4,16 +4,18 @@ feature 'Merge immediately', :feature, :js do let(:user) { create(:user) } let(:project) { create(:project, :public) } - let(:merge_request) do + let!(:merge_request) do create(:merge_request_with_diffs, source_project: project, author: user, - title: 'Bug NS-04') + title: 'Bug NS-04', + head_pipeline: pipeline, + source_branch: pipeline.ref) end let(:pipeline) do create(:ci_pipeline, project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch) + ref: 'master', + sha: project.repository.commit('master').id) end before { project.team << [user, :master] } diff --git a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb index b33d7f90a31..11b6f0c0a64 100644 --- a/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_pipeline_succeeds_spec.rb @@ -16,7 +16,10 @@ feature 'Merge When Pipeline Succeeds', :feature, :js do ref: merge_request.source_branch) end - before { project.team << [user, :master] } + before do + project.add_master(user) + merge_request.update(head_pipeline_id: pipeline.id) + end context 'when there is active pipeline for merge request' do background do diff --git a/spec/features/merge_requests/mini_pipeline_graph_spec.rb b/spec/features/merge_requests/mini_pipeline_graph_spec.rb index 449a60c1d05..5b2798af32f 100644 --- a/spec/features/merge_requests/mini_pipeline_graph_spec.rb +++ b/spec/features/merge_requests/mini_pipeline_graph_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' feature 'Mini Pipeline Graph', :js, :feature do let(:user) { create(:user) } let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) } let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running', sha: project.commit.id) } let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') } diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb index 187e927dac4..cdda0542c51 100644 --- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb +++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds_spec.rb @@ -31,6 +31,8 @@ feature 'Only allow merge requests to be merged if the pipeline succeeds', featu status: status) end + before { merge_request.update(head_pipeline: pipeline) } + context 'when merge requests can only be merged if the pipeline succeeds' do before do project.update_attribute(:only_allow_merge_if_pipeline_succeeds, true) diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb index 3fcdc9f2c61..ae799584c0f 100644 --- a/spec/features/merge_requests/widget_spec.rb +++ b/spec/features/merge_requests/widget_spec.rb @@ -91,6 +91,8 @@ describe 'Merge request', :feature, :js do statuses: [commit_status]) create(:ci_build, :pending, pipeline: pipeline) + merge_request.update(head_pipeline: pipeline) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) end @@ -103,10 +105,15 @@ describe 'Merge request', :feature, :js do context 'when merge request is in the blocked pipeline state' do before do - create(:ci_pipeline, project: project, - sha: merge_request.diff_head_sha, - ref: merge_request.source_branch, - status: :manual) + pipeline = create( + :ci_pipeline, + project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch, + status: :manual + ) + + merge_request.update(head_pipeline: pipeline) visit namespace_project_merge_request_path(project.namespace, project, @@ -131,6 +138,8 @@ describe 'Merge request', :feature, :js do statuses: [commit_status]) create(:ci_build, :pending, pipeline: pipeline) + merge_request.update(head_pipeline: pipeline) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) end diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb index c7a32a65e49..b7ae5f0b925 100644 --- a/spec/features/projects/members/sorting_spec.rb +++ b/spec/features/projects/members/sorting_spec.rb @@ -68,7 +68,7 @@ feature 'Projects > Members > Sorting', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending') end - scenario 'sorts by recent sign in' do + scenario 'sorts by recent sign in', :redis do visit_members_list(sort: :recent_sign_in) expect(first_member).to include(master.name) @@ -76,7 +76,7 @@ feature 'Projects > Members > Sorting', feature: true do expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in') end - scenario 'sorts by oldest sign in' do + scenario 'sorts by oldest sign in', :redis do visit_members_list(sort: :oldest_sign_in) expect(first_member).to include(developer.name) diff --git a/spec/javascripts/pipelines/graph/graph_component_spec.js b/spec/javascripts/pipelines/graph/graph_component_spec.js index a756617e65e..6bd0eb86263 100644 --- a/spec/javascripts/pipelines/graph/graph_component_spec.js +++ b/spec/javascripts/pipelines/graph/graph_component_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import graphComponent from '~/pipelines/components/graph/graph_component.vue'; +import graphJSON from './mock_data'; describe('graph component', () => { preloadFixtures('static/graph.html.raw'); @@ -20,41 +21,7 @@ describe('graph component', () => { describe('with a successfull response', () => { const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify({ - details: { - stages: [{ - name: 'test', - title: 'test: passed', - status: { - icon: 'icon_status_success', - text: 'passed', - label: 'passed', - details_path: '/root/ci-mock/pipelines/123#test', - }, - path: '/root/ci-mock/pipelines/123#test', - groups: [{ - name: 'test', - size: 1, - jobs: [{ - id: 4153, - name: 'test', - status: { - icon: 'icon_status_success', - text: 'passed', - label: 'passed', - details_path: '/root/ci-mock/builds/4153', - action: { - icon: 'icon_action_retry', - title: 'Retry', - path: '/root/ci-mock/builds/4153/retry', - method: 'post', - }, - }, - }], - }], - }], - }, - }), { + next(request.respondWith(JSON.stringify(graphJSON), { status: 200, })); }; @@ -73,6 +40,18 @@ describe('graph component', () => { setTimeout(() => { expect(component.$el.classList.contains('js-pipeline-graph')).toEqual(true); + expect( + component.$el.querySelector('.stage-column:first-child').classList.contains('no-margin'), + ).toEqual(true); + + expect( + component.$el.querySelector('.stage-column:nth-child(2)').classList.contains('left-margin'), + ).toEqual(true); + + expect( + component.$el.querySelector('.stage-column:nth-child(2) .build:nth-child(1)').classList.contains('left-connector'), + ).toEqual(true); + expect(component.$el.querySelector('loading-icon')).toBe(null); expect(component.$el.querySelector('.stage-column-list')).toBeDefined(); diff --git a/spec/javascripts/pipelines/graph/mock_data.js b/spec/javascripts/pipelines/graph/mock_data.js new file mode 100644 index 00000000000..56c522b7f77 --- /dev/null +++ b/spec/javascripts/pipelines/graph/mock_data.js @@ -0,0 +1,232 @@ +/* eslint-disable quote-props, quotes, comma-dangle */ +export default { + "id": 123, + "user": { + "name": "Root", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": null, + "web_url": "http://localhost:3000/root" + }, + "active": false, + "coverage": null, + "path": "/root/ci-mock/pipelines/123", + "details": { + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/pipelines/123", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico" + }, + "duration": 9, + "finished_at": "2017-04-19T14:30:27.542Z", + "stages": [{ + "name": "test", + "title": "test: passed", + "groups": [{ + "name": "test", + "size": 1, + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4153", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4153/retry", + "method": "post" + } + }, + "jobs": [{ + "id": 4153, + "name": "test", + "build_path": "/root/ci-mock/builds/4153", + "retry_path": "/root/ci-mock/builds/4153/retry", + "playable": false, + "created_at": "2017-04-13T09:25:18.959Z", + "updated_at": "2017-04-13T09:25:23.118Z", + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4153", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4153/retry", + "method": "post" + } + } + }] + }], + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/pipelines/123#test", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico" + }, + "path": "/root/ci-mock/pipelines/123#test", + "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=test" + }, { + "name": "deploy", + "title": "deploy: passed", + "groups": [{ + "name": "deploy to production", + "size": 1, + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4166", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4166/retry", + "method": "post" + } + }, + "jobs": [{ + "id": 4166, + "name": "deploy to production", + "build_path": "/root/ci-mock/builds/4166", + "retry_path": "/root/ci-mock/builds/4166/retry", + "playable": false, + "created_at": "2017-04-19T14:29:46.463Z", + "updated_at": "2017-04-19T14:30:27.498Z", + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4166", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4166/retry", + "method": "post" + } + } + }] + }, { + "name": "deploy to staging", + "size": 1, + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4159", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4159/retry", + "method": "post" + } + }, + "jobs": [{ + "id": 4159, + "name": "deploy to staging", + "build_path": "/root/ci-mock/builds/4159", + "retry_path": "/root/ci-mock/builds/4159/retry", + "playable": false, + "created_at": "2017-04-18T16:32:08.420Z", + "updated_at": "2017-04-18T16:32:12.631Z", + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/builds/4159", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico", + "action": { + "icon": "icon_action_retry", + "title": "Retry", + "path": "/root/ci-mock/builds/4159/retry", + "method": "post" + } + } + }] + }], + "status": { + "icon": "icon_status_success", + "text": "passed", + "label": "passed", + "group": "success", + "has_details": true, + "details_path": "/root/ci-mock/pipelines/123#deploy", + "favicon": "/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico" + }, + "path": "/root/ci-mock/pipelines/123#deploy", + "dropdown_path": "/root/ci-mock/pipelines/123/stage.json?stage=deploy" + }], + "artifacts": [], + "manual_actions": [{ + "name": "deploy to production", + "path": "/root/ci-mock/builds/4166/play", + "playable": false + }] + }, + "flags": { + "latest": true, + "triggered": false, + "stuck": false, + "yaml_errors": false, + "retryable": false, + "cancelable": false + }, + "ref": { + "name": "master", + "path": "/root/ci-mock/tree/master", + "tag": false, + "branch": true + }, + "commit": { + "id": "798e5f902592192afaba73f4668ae30e56eae492", + "short_id": "798e5f90", + "title": "Merge branch 'new-branch' into 'master'\r", + "created_at": "2017-04-13T10:25:17.000+01:00", + "parent_ids": ["54d483b1ed156fbbf618886ddf7ab023e24f8738", "c8e2d38a6c538822e81c57022a6e3a0cfedebbcc"], + "message": "Merge branch 'new-branch' into 'master'\r\n\r\nAdd new file\r\n\r\nSee merge request !1", + "author_name": "Root", + "author_email": "admin@example.com", + "authored_date": "2017-04-13T10:25:17.000+01:00", + "committer_name": "Root", + "committer_email": "admin@example.com", + "committed_date": "2017-04-13T10:25:17.000+01:00", + "author": { + "name": "Root", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": null, + "web_url": "http://localhost:3000/root" + }, + "author_gravatar_url": null, + "commit_url": "http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492", + "commit_path": "/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492" + }, + "created_at": "2017-04-13T09:25:18.881Z", + "updated_at": "2017-04-19T14:30:27.561Z" +}; diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 6677fe9c1ee..4eb8ad3d9e4 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,12 +1,10 @@ /* eslint-disable space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign, max-len */ (function() { - var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; - this.MockU2FDevice = (function() { function MockU2FDevice() { - this.respondToAuthenticateRequest = bind(this.respondToAuthenticateRequest, this); - this.respondToRegisterRequest = bind(this.respondToRegisterRequest, this); + this.respondToAuthenticateRequest = this.respondToAuthenticateRequest.bind(this); + this.respondToRegisterRequest = this.respondToRegisterRequest.bind(this); window.u2f || (window.u2f = {}); window.u2f.register = (function(_this) { return function(appId, registerRequests, signRequests, callback) { diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js index 3d5f71babfb..2f971b39d16 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js @@ -9,6 +9,7 @@ const deploymentMockData = [ name: 'review/diplo', url: '/root/acets-review-apps/environments/15', stop_url: '/root/acets-review-apps/environments/15/stop', + metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics', external_url: 'http://diplo.', external_url_formatted: 'diplo.', deployed_at: '2017-03-22T22:44:42.258Z', @@ -156,6 +157,7 @@ describe('MRWidgetDeployment', () => { expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url); expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted); expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at)); + expect(el.querySelector('.js-mr-memory-usage')).toBeDefined(); expect(el.querySelector('button')).toBeDefined(); }); @@ -165,6 +167,7 @@ describe('MRWidgetDeployment', () => { Vue.nextTick(() => { expect(el.querySelectorAll('.ci-widget').length).toEqual(3); + expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3); done(); }); }); @@ -176,6 +179,7 @@ describe('MRWidgetDeployment', () => { expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0); expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0); expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0); + expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0); expect(el.querySelectorAll('.button').length).toEqual(0); done(); }); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js new file mode 100644 index 00000000000..da9dff18ada --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js @@ -0,0 +1,184 @@ +import Vue from 'vue'; +import memoryUsageComponent from '~/vue_merge_request_widget/components/mr_widget_memory_usage'; +import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; + +const url = '/root/acets-review-apps/environments/15/deployments/1/metrics'; + +const metricsMockData = { + success: true, + metrics: { + memory_values: [ + { + metric: {}, + values: [ + [1493716685, '4.30859375'], + ], + }, + ], + }, + last_update: '2017-05-02T12:34:49.628Z', + deployment_time: 1493718485, +}; + +const createComponent = () => { + const Component = Vue.extend(memoryUsageComponent); + + return new Component({ + el: document.createElement('div'), + propsData: { + metricsUrl: url, + memoryMetrics: [], + deploymentTime: 0, + hasMetrics: false, + loadFailed: false, + loadingMetrics: true, + backOffRequestCounter: 0, + }, + }); +}; + +const messages = { + loadingMetrics: 'Loading deployment statistics.', + hasMetrics: 'Deployment memory usage:', + loadFailed: 'Failed to load deployment statistics.', + metricsUnavailable: 'Deployment statistics are not available currently.', +}; + +describe('MemoryUsage', () => { + let vm; + let el; + + beforeEach(() => { + vm = createComponent(); + el = vm.$el; + }); + + describe('props', () => { + it('should have props with defaults', () => { + const { metricsUrl } = memoryUsageComponent.props; + const MetricsUrlTypeClass = metricsUrl.type; + + Vue.nextTick(() => { + expect(new MetricsUrlTypeClass() instanceof String).toBeTruthy(); + expect(metricsUrl.required).toBeTruthy(); + }); + }); + }); + + describe('data', () => { + it('should have default data', () => { + const data = memoryUsageComponent.data(); + + expect(Array.isArray(data.memoryMetrics)).toBeTruthy(); + expect(data.memoryMetrics.length).toBe(0); + + expect(typeof data.deploymentTime).toBe('number'); + expect(data.deploymentTime).toBe(0); + + expect(typeof data.hasMetrics).toBe('boolean'); + expect(data.hasMetrics).toBeFalsy(); + + expect(typeof data.loadFailed).toBe('boolean'); + expect(data.loadFailed).toBeFalsy(); + + expect(typeof data.loadingMetrics).toBe('boolean'); + expect(data.loadingMetrics).toBeTruthy(); + + expect(typeof data.backOffRequestCounter).toBe('number'); + expect(data.backOffRequestCounter).toBe(0); + }); + }); + + describe('methods', () => { + const { metrics, deployment_time } = metricsMockData; + + describe('computeGraphData', () => { + it('should populate sparkline graph', () => { + vm.computeGraphData(metrics, deployment_time); + const { hasMetrics, memoryMetrics, deploymentTime } = vm; + + expect(hasMetrics).toBeTruthy(); + expect(memoryMetrics.length > 0).toBeTruthy(); + expect(deploymentTime).toEqual(deployment_time); + }); + }); + + describe('loadMetrics', () => { + const returnServicePromise = () => new Promise((resolve) => { + resolve({ + json() { + return metricsMockData; + }, + }); + }); + + it('should load metrics data using MRWidgetService', (done) => { + spyOn(MRWidgetService, 'fetchMetrics').and.returnValue(returnServicePromise(true)); + spyOn(vm, 'computeGraphData'); + + vm.loadMetrics(); + setTimeout(() => { + expect(MRWidgetService.fetchMetrics).toHaveBeenCalledWith(url); + expect(vm.computeGraphData).toHaveBeenCalledWith(metrics, deployment_time); + done(); + }, 333); + }); + }); + }); + + describe('template', () => { + it('should render template elements correctly', () => { + expect(el.classList.contains('mr-memory-usage')).toBeTruthy(); + expect(el.querySelector('.js-usage-info')).toBeDefined(); + }); + + it('should show loading metrics message while metrics are being loaded', (done) => { + vm.loadingMetrics = true; + vm.hasMetrics = false; + vm.loadFailed = false; + + Vue.nextTick(() => { + expect(el.querySelector('.js-usage-info.usage-info-loading')).toBeDefined(); + expect(el.querySelector('.js-usage-info .usage-info-load-spinner')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadingMetrics); + done(); + }); + }); + + it('should show deployment memory usage when metrics are loaded', (done) => { + vm.loadingMetrics = false; + vm.hasMetrics = true; + vm.loadFailed = false; + + Vue.nextTick(() => { + expect(el.querySelector('.memory-graph-container')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.hasMetrics); + done(); + }); + }); + + it('should show failure message when metrics loading failed', (done) => { + vm.loadingMetrics = false; + vm.hasMetrics = false; + vm.loadFailed = true; + + Vue.nextTick(() => { + expect(el.querySelector('.js-usage-info.usage-info-failed')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.loadFailed); + done(); + }); + }); + + it('should show metrics unavailable message when metrics loading failed', (done) => { + vm.loadingMetrics = false; + vm.hasMetrics = false; + vm.loadFailed = false; + + Vue.nextTick(() => { + expect(el.querySelector('.js-usage-info.usage-info-unavailable')).toBeDefined(); + expect(el.querySelector('.js-usage-info').innerText).toContain(messages.metricsUnavailable); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/ci_action_icons_spec.js b/spec/javascripts/vue_shared/ci_action_icons_spec.js index 2e89a07e76e..3d53a5ab24d 100644 --- a/spec/javascripts/vue_shared/ci_action_icons_spec.js +++ b/spec/javascripts/vue_shared/ci_action_icons_spec.js @@ -2,6 +2,7 @@ import getActionIcon from '~/vue_shared/ci_action_icons'; import cancelSVG from 'icons/_icon_action_cancel.svg'; import retrySVG from 'icons/_icon_action_retry.svg'; import playSVG from 'icons/_icon_action_play.svg'; +import stopSVG from 'icons/_icon_action_stop.svg'; describe('getActionIcon', () => { it('should return an empty string', () => { @@ -19,4 +20,8 @@ describe('getActionIcon', () => { it('should return play svg', () => { expect(getActionIcon('icon_action_play')).toEqual(playSVG); }); + + it('should render stop svg', () => { + expect(getActionIcon('icon_action_stop')).toEqual(stopSVG); + }); }); diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js new file mode 100644 index 00000000000..d46a3f2328e --- /dev/null +++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js @@ -0,0 +1,143 @@ +import Vue from 'vue'; +import memoryGraphComponent from '~/vue_shared/components/memory_graph'; +import { mockMetrics, mockMedian, mockMedianIndex } from './mock_data'; + +const defaultHeight = '25'; +const defaultWidth = '100'; + +const createComponent = () => { + const Component = Vue.extend(memoryGraphComponent); + + return new Component({ + el: document.createElement('div'), + propsData: { + metrics: [], + deploymentTime: 0, + width: '', + height: '', + pathD: '', + pathViewBox: '', + dotX: '', + dotY: '', + }, + }); +}; + +describe('MemoryGraph', () => { + let vm; + let el; + + beforeEach(() => { + vm = createComponent(); + el = vm.$el; + }); + + describe('props', () => { + it('should have props with defaults', (done) => { + const { metrics, deploymentTime, width, height } = memoryGraphComponent.props; + + Vue.nextTick(() => { + const typeClassMatcher = (propItem, expectedType) => { + const PropItemTypeClass = propItem.type; + expect(new PropItemTypeClass() instanceof expectedType).toBeTruthy(); + expect(propItem.required).toBeTruthy(); + }; + + typeClassMatcher(metrics, Array); + typeClassMatcher(deploymentTime, Number); + typeClassMatcher(width, String); + typeClassMatcher(height, String); + done(); + }); + }); + }); + + describe('data', () => { + it('should have default data', () => { + const data = memoryGraphComponent.data(); + const dataValidator = (dataItem, expectedType, defaultVal) => { + expect(typeof dataItem).toBe(expectedType); + expect(dataItem).toBe(defaultVal); + }; + + dataValidator(data.pathD, 'string', ''); + dataValidator(data.pathViewBox, 'string', ''); + dataValidator(data.dotX, 'string', ''); + dataValidator(data.dotY, 'string', ''); + }); + }); + + describe('computed', () => { + describe('getFormattedMedian', () => { + it('should show human readable median value based on provided median timestamp', () => { + vm.deploymentTime = mockMedian; + const formattedMedian = vm.getFormattedMedian; + expect(formattedMedian.indexOf('Deployed') > -1).toBeTruthy(); + expect(formattedMedian.indexOf('ago') > -1).toBeTruthy(); + }); + }); + }); + + describe('methods', () => { + describe('getMedianMetricIndex', () => { + it('should return index of closest metric timestamp to that of median', () => { + const matchingIndex = vm.getMedianMetricIndex(mockMedian, mockMetrics); + expect(matchingIndex).toBe(mockMedianIndex); + }); + }); + + describe('getGraphPlotValues', () => { + it('should return Object containing values to plot graph', () => { + const plotValues = vm.getGraphPlotValues(mockMedian, mockMetrics); + expect(plotValues.pathD).toBeDefined(); + expect(Array.isArray(plotValues.pathD)).toBeTruthy(); + + expect(plotValues.pathViewBox).toBeDefined(); + expect(typeof plotValues.pathViewBox).toBe('object'); + + expect(plotValues.dotX).toBeDefined(); + expect(typeof plotValues.dotX).toBe('number'); + + expect(plotValues.dotY).toBeDefined(); + expect(typeof plotValues.dotY).toBe('number'); + }); + }); + }); + + describe('template', () => { + it('should render template elements correctly', () => { + expect(el.classList.contains('memory-graph-container')).toBeTruthy(); + expect(el.querySelector('svg')).toBeDefined(); + }); + + it('should render graph when renderGraph is called internally', (done) => { + const { pathD, pathViewBox, dotX, dotY } = vm.getGraphPlotValues(mockMedian, mockMetrics); + vm.height = defaultHeight; + vm.width = defaultWidth; + vm.pathD = `M ${pathD}`; + vm.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`; + vm.dotX = dotX; + vm.dotY = dotY; + + Vue.nextTick(() => { + const svgEl = el.querySelector('svg'); + expect(svgEl).toBeDefined(); + expect(svgEl.getAttribute('height')).toBe(defaultHeight); + expect(svgEl.getAttribute('width')).toBe(defaultWidth); + + const pathEl = el.querySelector('path'); + expect(pathEl).toBeDefined(); + expect(pathEl.getAttribute('d')).toBe(`M ${pathD}`); + expect(pathEl.getAttribute('viewBox')).toBe(`0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`); + + const circleEl = el.querySelector('circle'); + expect(circleEl).toBeDefined(); + expect(circleEl.getAttribute('r')).toBe('1.5'); + expect(circleEl.getAttribute('tranform')).toBe('translate(0 -1)'); + expect(circleEl.getAttribute('cx')).toBe(`${dotX}`); + expect(circleEl.getAttribute('cy')).toBe(`${dotY}`); + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/mock_data.js b/spec/javascripts/vue_shared/components/mock_data.js new file mode 100644 index 00000000000..0d781bdca74 --- /dev/null +++ b/spec/javascripts/vue_shared/components/mock_data.js @@ -0,0 +1,69 @@ +/* eslint-disable */ + +export const mockMetrics = [ + [1493716685, '4.30859375'], + [1493716745, '4.30859375'], + [1493716805, '4.30859375'], + [1493716865, '4.30859375'], + [1493716925, '4.30859375'], + [1493716985, '4.30859375'], + [1493717045, '4.30859375'], + [1493717105, '4.30859375'], + [1493717165, '4.30859375'], + [1493717225, '4.30859375'], + [1493717285, '4.30859375'], + [1493717345, '4.30859375'], + [1493717405, '4.30859375'], + [1493717465, '4.30859375'], + [1493717525, '4.30859375'], + [1493717585, '4.30859375'], + [1493717645, '4.30859375'], + [1493717705, '4.30859375'], + [1493717765, '4.30859375'], + [1493717825, '4.30859375'], + [1493717885, '4.30859375'], + [1493717945, '4.30859375'], + [1493718005, '4.30859375'], + [1493718065, '4.30859375'], + [1493718125, '4.30859375'], + [1493718185, '4.30859375'], + [1493718245, '4.30859375'], + [1493718305, '4.234375'], + [1493718365, '4.234375'], + [1493718425, '4.234375'], + [1493718485, '4.234375'], + [1493718545, '4.243489583333333'], + [1493718605, '4.2109375'], + [1493718665, '4.2109375'], + [1493718725, '4.2109375'], + [1493718785, '4.26171875'], + [1493718845, '4.26171875'], + [1493718905, '4.26171875'], + [1493718965, '4.26171875'], + [1493719025, '4.26171875'], + [1493719085, '4.26171875'], + [1493719145, '4.26171875'], + [1493719205, '4.26171875'], + [1493719265, '4.26171875'], + [1493719325, '4.26171875'], + [1493719385, '4.26171875'], + [1493719445, '4.26171875'], + [1493719505, '4.26171875'], + [1493719565, '4.26171875'], + [1493719625, '4.26171875'], + [1493719685, '4.26171875'], + [1493719745, '4.26171875'], + [1493719805, '4.26171875'], + [1493719865, '4.26171875'], + [1493719925, '4.26171875'], + [1493719985, '4.26171875'], + [1493720045, '4.26171875'], + [1493720105, '4.26171875'], + [1493720165, '4.26171875'], + [1493720225, '4.26171875'], + [1493720285, '4.26171875'], +]; + +export const mockMedian = 1493718485; + +export const mockMedianIndex = 30; diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js index 96038718191..895e1c585b4 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import paginationComp from '~/vue_shared/components/table_pagination'; +import paginationComp from '~/vue_shared/components/table_pagination.vue'; import '~/lib/utils/common_utils'; describe('Pagination component', () => { diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index 0d56bdd0ebd..3610a0354e8 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -130,6 +130,8 @@ describe 'cycle analytics events' do end before do + merge_request.update(head_pipeline: pipeline) + create(:ci_build, pipeline: pipeline, status: :success, author: user) create(:ci_build, pipeline: pipeline, status: :success, author: user) @@ -226,6 +228,8 @@ describe 'cycle analytics events' do end before do + merge_request.update(head_pipeline: pipeline) + create(:ci_build, pipeline: pipeline, status: :success, author: user) create(:ci_build, pipeline: pipeline, status: :success, author: user) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 688e731bf15..34f617e23a5 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -85,6 +85,7 @@ merge_requests: - merge_requests_closing_issues - metrics - timelogs +- head_pipeline merge_request_diff: - merge_request pipelines: @@ -102,6 +103,7 @@ pipelines: - manual_actions - artifacts - pipeline_schedule +- merge_requests statuses: - project - pipeline diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 3af2a172e6d..d2ceb1cf9ae 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -158,6 +158,7 @@ MergeRequest: - time_estimate - last_edited_at - last_edited_by_id +- head_pipeline_id MergeRequestDiff: - id - state diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb new file mode 100644 index 00000000000..bd5f85b901d --- /dev/null +++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb') + +describe AddHeadPipelineForEachMergeRequest do + let(:migration) { described_class.new } + + let!(:project) { create(:empty_project) } + let!(:forked_project_link) { create(:forked_project_link, forked_from_project: project) } + let!(:other_project) { forked_project_link.forked_to_project } + + let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } + let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } + let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") } + let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") } + + let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") } + let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") } + let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") } + let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") } + + context "#up" do + context "when source_project and source_branch of pipeline are the same of merge request" do + it "sets head_pipeline_id of given merge requests" do + migration.up + + expect(mr_1.reload.head_pipeline_id).to eq(pipeline_1.id) + expect(mr_2.reload.head_pipeline_id).to eq(pipeline_3.id) + expect(mr_3.reload.head_pipeline_id).to eq(pipeline_4.id) + expect(mr_4.reload.head_pipeline_id).to be_nil + end + end + end +end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 208c8cb1c3d..06e990a0574 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1044,8 +1044,8 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do - merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref) allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' } + merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref) expect(pipeline.merge_requests).to eq([merge_request]) end diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index c2ba012a0e6..d0b919efcf9 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -14,6 +14,7 @@ describe 'CycleAnalytics#test', feature: true do issue = context.create(:issue, project: context.project) merge_request = context.create_merge_request_closing_issue(issue) pipeline = context.create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, project: context.project) + merge_request.update(head_pipeline: pipeline) { pipeline: pipeline, issue: issue } end, start_time_conditions: [["pipeline is started", -> (context, data) { data[:pipeline].run! }]], diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 6cf3dd30ead..ef349530761 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -760,13 +760,8 @@ describe MergeRequest, models: true do describe '#head_pipeline' do describe 'when the source project exists' do it 'returns the latest pipeline' do - pipeline = double(:ci_pipeline, ref: 'master') - - allow(subject).to receive(:diff_head_sha).and_return('123abc') - - expect(subject.source_project).to receive(:pipeline_for). - with('master', '123abc'). - and_return(pipeline) + pipeline = create(:ci_empty_pipeline, project: subject.source_project, ref: 'master', status: 'running', sha: "123abc") + subject.update(head_pipeline: pipeline) expect(subject.head_pipeline).to eq(pipeline) end @@ -1504,11 +1499,15 @@ describe MergeRequest, models: true do describe '#mergeable_with_slash_command?' do def create_pipeline(status) - create(:ci_pipeline_with_one_job, + pipeline = create(:ci_pipeline_with_one_job, project: project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, status: status) + + merge_request.update(head_pipeline: pipeline) + + pipeline end let(:project) { create(:project, :public, :repository, only_allow_merge_if_pipeline_succeeds: true) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 63e71f5ff2f..c7ddd17872b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -344,6 +344,35 @@ describe User, models: true do end end + describe '#update_tracked_fields!', :redis do + let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") } + let(:user) { create(:user) } + + it 'writes trackable attributes' do + expect do + user.update_tracked_fields!(request) + end.to change { user.reload.current_sign_in_at } + end + + it 'does not write trackable attributes when called a second time within the hour' do + user.update_tracked_fields!(request) + + expect do + user.update_tracked_fields!(request) + end.not_to change { user.reload.current_sign_in_at } + end + + it 'writes trackable attributes for a different user' do + user2 = create(:user) + + user.update_tracked_fields!(request) + + expect do + user2.update_tracked_fields!(request) + end.to change { user2.reload.current_sign_in_at } + end + end + shared_context 'user keys' do let(:user) { create(:user) } let!(:key) { create(:key, user: user) } diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index a4f85c22943..fbb69bc0920 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -98,7 +98,7 @@ describe 'OpenID Connect requests' do expect(@payload['sub']).to eq hashed_subject end - it 'includes the time of the last authentication' do + it 'includes the time of the last authentication', :redis do expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i end diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index c5a45949841..d92daa345b3 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -122,6 +122,7 @@ describe 'cycle analytics events', api: true do mr = create_merge_request_closing_issue(issue, commit_message: "References #{issue.to_reference}") pipeline = create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha) + mr.update(head_pipeline_id: pipeline.id) pipeline.run create(:ci_build, pipeline: pipeline, status: :success, author: user) diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index fa5014cee07..1ff1438ba06 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -34,6 +34,42 @@ describe Ci::CreatePipelineService, services: true do it { expect(pipeline).to have_attributes(status: 'pending') } it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) } + context '#update_merge_requests_head_pipeline' do + it 'updates head pipeline of each merge request' do + merge_request_1 = create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project) + merge_request_2 = create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project) + + head_pipeline = pipeline + + expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline) + expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline) + end + + context 'when there is no pipeline for source branch' do + it "does not update merge request head pipeline" do + merge_request = create(:merge_request, source_branch: 'other_branch', target_branch: "branch_1", source_project: project) + + head_pipeline = pipeline + + expect(merge_request.reload.head_pipeline).not_to eq(head_pipeline) + end + end + + context 'when merge request target project is different from source project' do + let!(:target_project) { create(:empty_project) } + let!(:forked_project_link) { create(:forked_project_link, forked_to_project: project, forked_from_project: target_project) } + + it 'updates head pipeline for merge request' do + merge_request = + create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project, target_project: target_project) + + head_pipeline = pipeline + + expect(merge_request.reload.head_pipeline).to eq(head_pipeline) + end + end + end + context 'auto-cancel enabled' do before do project.update(auto_cancel_pending_pipelines: 'enabled') diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8c2415b4e07..ab06f45dbb9 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -584,7 +584,7 @@ describe GitPushService, services: true do commit = double(:commit) diff = double(:diff, new_path: 'README.md') - expect(commit).to receive(:raw_diffs).with(deltas_only: true). + expect(commit).to receive(:raw_deltas). and_return([diff]) service.push_commits = [commit] diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 769b3193275..3ef5135e6a3 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -82,6 +82,10 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do sha: merge_request_head, status: 'success') end + before do + mr_merge_if_green_enabled.update(head_pipeline: triggering_pipeline) + end + it "merges all merge requests with merge when the pipeline succeeds enabled" do expect(MergeWorker).to receive(:perform_async) service.trigger(triggering_pipeline) @@ -124,6 +128,8 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do sha: mr_conflict.diff_head_sha, status: 'success') end + before { mr_conflict.update(head_pipeline: conflict_pipeline) } + it 'does not merge the merge request' do expect(MergeWorker).not_to receive(:perform_async) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 31487c0f794..07f5440cc36 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -174,11 +174,13 @@ describe MergeRequests::UpdateService, services: true do context 'with active pipeline' do before do service_mock = double - create(:ci_pipeline_with_one_job, + pipeline = create(:ci_pipeline_with_one_job, project: project, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) + merge_request.update(head_pipeline: pipeline) + expect(MergeRequests::MergeWhenPipelineSucceedsService).to receive(:new).with(project, user). and_return(service_mock) expect(service_mock).to receive(:execute).with(merge_request) diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb index 5dbc0da95c2..ef71125c0b6 100644 --- a/spec/workers/pipeline_metrics_worker_spec.rb +++ b/spec/workers/pipeline_metrics_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe PipelineMetricsWorker do let(:project) { create(:project, :repository) } - let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref, head_pipeline: pipeline) } let(:pipeline) do create(:ci_empty_pipeline, |