diff options
Diffstat (limited to 'spec')
30 files changed, 674 insertions, 256 deletions
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index a38ae2eb990..b65e9e0dfc0 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -260,6 +260,7 @@ describe Projects::IssuesController do before { allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false) } it 'rejects an issue recognized as a spam' do + expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true) expect { update_spam_issue }.not_to change{ issue.reload.title } end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 7211acc53dc..4a737587899 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -69,18 +69,11 @@ describe Projects::JobsController do Ci::Build::AVAILABLE_STATUSES.each do |status| create_build(status, status) end - - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! end - it "verifies number of queries" do + it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { get_index } - expect(recorded.count).to be_within(5).of(8) + expect(recorded.count).to be_within(5).of(7) end def create_build(name, status) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 0a64fe2beda..6e1c91738db 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -119,10 +119,8 @@ describe Projects::MergeRequestsController do end end - context 'number of queries' do + context 'number of queries', :request_store do it 'verifies number of queries' do - RequestStore.begin! - # pre-create objects merge_request @@ -130,9 +128,6 @@ describe Projects::MergeRequestsController do expect(recorded.count).to be_within(5).of(30) expect(recorded.cached_count).to eq(0) - - RequestStore.end! - RequestStore.clear! end end end diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index c880da1e36a..954f89e3854 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -49,21 +49,14 @@ describe Projects::PipelinesController do expect(json_response['details']).to have_key 'stages' end - context 'when the pipeline has multiple stages and groups' do + context 'when the pipeline has multiple stages and groups', :request_store do before do - RequestStore.begin! - create_build('build', 0, 'build') create_build('test', 1, 'rspec 0') create_build('deploy', 2, 'production') create_build('post deploy', 3, 'pages 0') end - after do - RequestStore.end! - RequestStore.clear! - end - let(:project) { create(:project) } let(:pipeline) do create(:ci_empty_pipeline, project: project, user: user, sha: project.commit.id) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 0eda46649db..727ae7081b0 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -5,6 +5,7 @@ feature 'Jobs', :feature do let(:user) { create(:user) } let(:user_access_level) { :developer } let(:project) { create(:project) } + let(:namespace) { project.namespace } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, :trace, pipeline: pipeline) } @@ -113,10 +114,16 @@ feature 'Jobs', :feature do describe "GET /:project/jobs/:id" do context "Job from project" do + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + before do visit namespace_project_job_path(project.namespace, project, build) end + it 'shows status name', :js do + expect(page).to have_css('.ci-status.ci-success', text: 'passed') + end + it 'shows commit`s data' do expect(page.status_code).to eq(200) expect(page).to have_content pipeline.sha[0..7] @@ -129,6 +136,48 @@ feature 'Jobs', :feature do end end + context 'when job is not running', :js do + let(:build) { create(:ci_build, :success, pipeline: pipeline) } + + before do + visit namespace_project_job_path(project.namespace, project, build) + end + + it 'shows retry button' do + expect(page).to have_link('Retry') + end + + context 'if build passed' do + it 'does not show New issue button' do + expect(page).not_to have_link('New issue') + end + end + + context 'if build failed' do + let(:build) { create(:ci_build, :failed, pipeline: pipeline) } + + before do + visit namespace_project_job_path(namespace, project, build) + end + + it 'shows New issue button' do + expect(page).to have_link('New issue') + end + + it 'links to issues/new with the title and description filled in' do + button_title = "Build Failed ##{build.id}" + build_path = namespace_project_job_path(namespace, project, build) + options = { issue: { title: button_title, description: build_path } } + + href = new_namespace_project_issue_path(namespace, project, options) + + page.within('.header-action-buttons') do + expect(find('.js-new-issue')['href']).to include(href) + end + end + end + end + context "Job from other project" do before do visit namespace_project_job_path(project.namespace, project, build2) @@ -305,63 +354,38 @@ feature 'Jobs', :feature do end end - describe "POST /:project/jobs/:id/cancel" do + describe "POST /:project/jobs/:id/cancel", :js do context "Job from project" do before do build.run! visit namespace_project_job_path(project.namespace, project, build) - click_link "Cancel" + find('.js-cancel-job').click() end it 'loads the page and shows all needed controls' do expect(page.status_code).to eq(200) - expect(page).to have_content 'canceled' expect(page).to have_content 'Retry' end end - - context "Job from other project" do - before do - build.run! - visit namespace_project_job_path(project.namespace, project, build) - page.driver.post(cancel_namespace_project_job_path(project.namespace, project, build2)) - end - - it { expect(page.status_code).to eq(404) } - end end describe "POST /:project/jobs/:id/retry" do - context "Job from project" do + context "Job from project", :js do before do build.run! visit namespace_project_job_path(project.namespace, project, build) - click_link 'Cancel' - page.within('.build-header') do - click_link 'Retry job' - end + find('.js-cancel-job').click() + find('.js-retry-button').trigger('click') end - it 'shows the right status and buttons' do + it 'shows the right status and buttons', :js do expect(page).to have_http_status(200) - expect(page).to have_content 'pending' page.within('aside.right-sidebar') do expect(page).to have_content 'Cancel' end end end - context "Job from other project" do - before do - build.run! - visit namespace_project_job_path(project.namespace, project, build) - click_link 'Cancel' - page.driver.post(retry_namespace_project_job_path(project.namespace, project, build2)) - end - - it { expect(page).to have_http_status(404) } - end - context "Job that current user is not allowed to retry" do before do build.run! @@ -435,20 +459,17 @@ feature 'Jobs', :feature do Capybara.current_session.driver.headers = { 'X-Sendfile-Type' => 'X-Sendfile' } build.run! - - allow_any_instance_of(Gitlab::Ci::Trace).to receive(:paths) - .and_return(paths) - - visit namespace_project_job_path(project.namespace, project, build) end context 'when build has trace in file', :js do - let(:paths) do - [existing_file] - end - before do - find('.js-raw-link-controller').click() + allow_any_instance_of(Gitlab::Ci::Trace) + .to receive(:paths) + .and_return([existing_file]) + + visit namespace_project_job_path(namespace, project, build) + + find('.js-raw-link-controller').click end it 'sends the right headers' do @@ -458,11 +479,17 @@ feature 'Jobs', :feature do end end - context 'when job has trace in DB' do - let(:paths) { [] } + context 'when job has trace in the database', :js do + before do + allow_any_instance_of(Gitlab::Ci::Trace) + .to receive(:paths) + .and_return([]) + + visit namespace_project_job_path(namespace, project, build) + end it 'sends the right headers' do - expect(page.status_code).not_to have_selector('.js-raw-link-controller') + expect(page).not_to have_selector('.js-raw-link-controller') end end end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index bb4b2aed0e3..feb2fe8a7d1 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -333,29 +333,6 @@ describe 'Dashboard Todos', feature: true do end end - context 'User have large number of todos' do - before do - create_list(:todo, 101, :mentioned, user: user, project: project, target: issue, author: author) - - login_as(user) - visit dashboard_todos_path - end - - it 'shows 99+ for count >= 100 in notification' do - expect(page).to have_selector('.todos-count', text: '99+') - end - - it 'shows exact number in To do tab' do - expect(page).to have_selector('.todos-pending .badge', text: '101') - end - - it 'shows exact number for count < 100' do - 3.times { first('.js-done-todo').click } - - expect(page).to have_selector('.todos-count', text: '98') - end - end - context 'User has a Build Failed todo' do let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index 50060a0925d..18a41ca24e3 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -1,6 +1,19 @@ require "spec_helper" describe TodosHelper do + describe '#todos_count_format' do + it 'shows fuzzy count for 100 or more items' do + expect(helper.todos_count_format(100)).to eq '99+' + expect(helper.todos_count_format(1000)).to eq '99+' + end + + it 'shows exact count for 99 or fewer items' do + expect(helper.todos_count_format(99)).to eq '99' + expect(helper.todos_count_format(50)).to eq '50' + expect(helper.todos_count_format(1)).to eq '1' + end + end + describe '#todo_projects_options' do let(:projects) { create_list(:empty_project, 3) } let(:user) { create(:user) } diff --git a/spec/helpers/u2f_helper_spec.rb b/spec/helpers/u2f_helper_spec.rb new file mode 100644 index 00000000000..0d65b4fe0b8 --- /dev/null +++ b/spec/helpers/u2f_helper_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe U2fHelper do + describe 'when not on mobile' do + it 'does not inject u2f on chrome 40' do + device = double(mobile?: false) + browser = double(chrome?: true, opera?: false, version: 40, device: device) + allow(helper).to receive(:browser).and_return(browser) + expect(helper.inject_u2f_api?).to eq false + end + + it 'injects u2f on chrome 41' do + device = double(mobile?: false) + browser = double(chrome?: true, opera?: false, version: 41, device: device) + allow(helper).to receive(:browser).and_return(browser) + expect(helper.inject_u2f_api?).to eq true + end + + it 'does not inject u2f on opera 39' do + device = double(mobile?: false) + browser = double(chrome?: false, opera?: true, version: 39, device: device) + allow(helper).to receive(:browser).and_return(browser) + expect(helper.inject_u2f_api?).to eq false + end + + it 'injects u2f on opera 40' do + device = double(mobile?: false) + browser = double(chrome?: false, opera?: true, version: 40, device: device) + allow(helper).to receive(:browser).and_return(browser) + expect(helper.inject_u2f_api?).to eq true + end + end + + describe 'when on mobile' do + it 'does not inject u2f on chrome 41' do + device = double(mobile?: true) + browser = double(chrome?: true, opera?: false, version: 41, device: device) + allow(helper).to receive(:browser).and_return(browser) + expect(helper.inject_u2f_api?).to eq false + end + + it 'does not inject u2f on opera 40' do + device = double(mobile?: true) + browser = double(chrome?: false, opera?: true, version: 40, device: device) + allow(helper).to receive(:browser).and_return(browser) + expect(helper.inject_u2f_api?).to eq false + end + end +end diff --git a/spec/javascripts/build_spec.js b/spec/javascripts/build_spec.js index 4c8a48580d7..be90dbdd88a 100644 --- a/spec/javascripts/build_spec.js +++ b/spec/javascripts/build_spec.js @@ -132,23 +132,6 @@ describe('Build', () => { expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/); expect($('#build-trace .js-build-output').text()).toMatch(/Different/); }); - - it('reloads the page when the build is done', () => { - spyOn(gl.utils, 'visitUrl'); - const deferred = $.Deferred(); - - spyOn($, 'ajax').and.returnValue(deferred.promise()); - deferred.resolve({ - html: '<span>Final</span>', - status: 'passed', - append: true, - complete: true, - }); - - this.build = new Build(); - - expect(gl.utils.visitUrl).toHaveBeenCalledWith(BUILD_URL); - }); }); describe('truncated information', () => { diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js index c82ad0bea48..e54ea11b08c 100644 --- a/spec/javascripts/datetime_utility_spec.js +++ b/spec/javascripts/datetime_utility_spec.js @@ -1,4 +1,4 @@ -import '~/lib/utils/datetime_utility'; +import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; (() => { describe('Date time utils', () => { @@ -82,4 +82,13 @@ import '~/lib/utils/datetime_utility'; }); }); }); + + describe('timeIntervalInWords', () => { + it('should return string with number of minutes and seconds', () => { + expect(timeIntervalInWords(9.54)).toEqual('9 seconds'); + expect(timeIntervalInWords(1)).toEqual('1 second'); + expect(timeIntervalInWords(200)).toEqual('3 minutes 20 seconds'); + expect(timeIntervalInWords(6008)).toEqual('100 minutes 8 seconds'); + }); + }); })(); diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js new file mode 100644 index 00000000000..c7179b3e03d --- /dev/null +++ b/spec/javascripts/jobs/header_spec.js @@ -0,0 +1,63 @@ +import Vue from 'vue'; +import headerComponent from '~/jobs/components/header.vue'; + +describe('Job details header', () => { + let HeaderComponent; + let vm; + let props; + + beforeEach(() => { + HeaderComponent = Vue.extend(headerComponent); + + const threeWeeksAgo = new Date(); + threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); + + props = { + job: { + status: { + group: 'failed', + icon: 'ci-status-failed', + label: 'failed', + text: 'failed', + details_path: 'path', + }, + id: 123, + created_at: threeWeeksAgo.toISOString(), + user: { + web_url: 'path', + name: 'Foo', + username: 'foobar', + email: 'foo@bar.com', + avatar_url: 'link', + }, + retry_path: 'path', + new_issue_path: 'path', + }, + isLoading: false, + }; + + vm = new HeaderComponent({ propsData: props }).$mount(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render provided job information', () => { + expect( + vm.$el.querySelector('.header-main-content').textContent.replace(/\s+/g, ' ').trim(), + ).toEqual('failed Job #123 triggered 3 weeks ago by Foo'); + }); + + it('should render retry link', () => { + expect( + vm.$el.querySelector('.js-retry-button').getAttribute('href'), + ).toEqual(props.job.retry_path); + }); + + it('should render new issue link', () => { + expect( + vm.$el.querySelector('.js-new-issue').getAttribute('href'), + ).toEqual(props.job.new_issue_path); + }); +}); diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js new file mode 100644 index 00000000000..1d7fa7e12fc --- /dev/null +++ b/spec/javascripts/jobs/job_details_mediator_spec.js @@ -0,0 +1,43 @@ +import Vue from 'vue'; +import JobMediator from '~/jobs/job_details_mediator'; +import job from './mock_data'; + +describe('JobMediator', () => { + let mediator; + + beforeEach(() => { + mediator = new JobMediator({ endpoint: 'foo' }); + }); + + it('should set defaults', () => { + expect(mediator.store).toBeDefined(); + expect(mediator.service).toBeDefined(); + expect(mediator.options).toEqual({ endpoint: 'foo' }); + expect(mediator.state.isLoading).toEqual(false); + }); + + describe('request and store data', () => { + const interceptor = (request, next) => { + next(request.respondWith(JSON.stringify(job), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(interceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor); + }); + + it('should store received data', (done) => { + mediator.fetchJob(); + + setTimeout(() => { + expect(mediator.store.state.job).toEqual(job); + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/jobs/job_store_spec.js b/spec/javascripts/jobs/job_store_spec.js new file mode 100644 index 00000000000..d00faf29d1e --- /dev/null +++ b/spec/javascripts/jobs/job_store_spec.js @@ -0,0 +1,26 @@ +import JobStore from '~/jobs/stores/job_store'; +import job from './mock_data'; + +describe('Job Store', () => { + let store; + + beforeEach(() => { + store = new JobStore(); + }); + + it('should set defaults', () => { + expect(store.state.job).toEqual({}); + }); + + describe('storeJob', () => { + it('should store empty object if none is provided', () => { + store.storeJob(); + expect(store.state.job).toEqual({}); + }); + + it('should store provided argument', () => { + store.storeJob(job); + expect(store.state.job).toEqual(job); + }); + }); +}); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js new file mode 100644 index 00000000000..17e4ef26b2c --- /dev/null +++ b/spec/javascripts/jobs/mock_data.js @@ -0,0 +1,123 @@ +const threeWeeksAgo = new Date(); +threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21); + +export default { + id: 4757, + name: 'test', + build_path: '/root/ci-mock/-/jobs/4757', + retry_path: '/root/ci-mock/-/jobs/4757/retry', + cancel_path: '/root/ci-mock/-/jobs/4757/cancel', + new_issue_path: '/root/ci-mock/issues/new', + playable: false, + created_at: threeWeeksAgo.toISOString(), + updated_at: threeWeeksAgo.toISOString(), + finished_at: threeWeeksAgo.toISOString(), + queued: 9.54, + status: { + icon: 'icon_status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: '/root/ci-mock/-/jobs/4757', + favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', + action: { + icon: 'icon_action_retry', + title: 'Retry', + path: '/root/ci-mock/-/jobs/4757/retry', + method: 'post', + }, + }, + coverage: 20, + erased_at: threeWeeksAgo.toISOString(), + duration: 6.785563, + tags: ['tag'], + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + erase_path: '/root/ci-mock/-/jobs/4757/erase', + artifacts: [null], + runner: { + id: 1, + description: 'local ci runner', + edit_path: '/root/ci-mock/runners/1/edit', + }, + pipeline: { + id: 140, + user: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + active: false, + coverage: null, + source: 'unknown', + created_at: '2017-05-24T09:59:58.634Z', + updated_at: '2017-06-01T17:32:00.062Z', + path: '/root/ci-mock/pipelines/140', + flags: { + latest: true, + stuck: false, + yaml_errors: false, + retryable: false, + cancelable: false, + }, + details: { + status: { + icon: 'icon_status_success', + text: 'passed', + label: 'passed', + group: 'success', + has_details: true, + details_path: '/root/ci-mock/pipelines/140', + favicon: '/assets/ci_favicons/dev/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.ico', + }, + duration: 6, + finished_at: '2017-06-01T17:32:00.042Z', + }, + ref: { + name: 'abc', + path: '/root/ci-mock/commits/abc', + tag: false, + branch: true, + }, + commit: { + id: 'c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + short_id: 'c5864777', + title: 'Add new file', + created_at: '2017-05-24T10:59:52.000+01:00', + parent_ids: ['798e5f902592192afaba73f4668ae30e56eae492'], + message: 'Add new file', + author_name: 'Root', + author_email: 'admin@example.com', + authored_date: '2017-05-24T10:59:52.000+01:00', + committer_name: 'Root', + committer_email: 'admin@example.com', + committed_date: '2017-05-24T10:59:52.000+01:00', + author: { + name: 'Root', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + web_url: 'http://localhost:3000/root', + }, + author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon', + commit_url: 'http://localhost:3000/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', + }, + }, + merge_request: { + iid: 2, + path: '/root/ci-mock/merge_requests/2', + }, + raw_path: '/root/ci-mock/builds/4757/raw', +}; diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js new file mode 100644 index 00000000000..3ac65709c4a --- /dev/null +++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js @@ -0,0 +1,40 @@ +import Vue from 'vue'; +import sidebarDetailRow from '~/jobs/components/sidebar_detail_row.vue'; + +describe('Sidebar detail row', () => { + let SidebarDetailRow; + let vm; + + beforeEach(() => { + SidebarDetailRow = Vue.extend(sidebarDetailRow); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render no title', () => { + vm = new SidebarDetailRow({ + propsData: { + value: 'this is the value', + }, + }).$mount(); + + expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toEqual('this is the value'); + }); + + beforeEach(() => { + vm = new SidebarDetailRow({ + propsData: { + title: 'this is the title', + value: 'this is the value', + }, + }).$mount(); + }); + + it('should render provided title and value', () => { + expect( + vm.$el.textContent.replace(/\s+/g, ' ').trim(), + ).toEqual('this is the title: this is the value'); + }); +}); diff --git a/spec/javascripts/jobs/sidebar_details_block_spec.js b/spec/javascripts/jobs/sidebar_details_block_spec.js new file mode 100644 index 00000000000..95532ef5382 --- /dev/null +++ b/spec/javascripts/jobs/sidebar_details_block_spec.js @@ -0,0 +1,111 @@ +import Vue from 'vue'; +import sidebarDetailsBlock from '~/jobs/components/sidebar_details_block.vue'; +import job from './mock_data'; + +describe('Sidebar details block', () => { + let SidebarComponent; + let vm; + + function trimWhitespace(element) { + return element.textContent.replace(/\s+/g, ' ').trim(); + } + + beforeEach(() => { + SidebarComponent = Vue.extend(sidebarDetailsBlock); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('when it is loading', () => { + it('should render a loading spinner', () => { + vm = new SidebarComponent({ + propsData: { + job: {}, + isLoading: true, + }, + }).$mount(); + + expect(vm.$el.querySelector('.fa-spinner')).toBeDefined(); + }); + }); + + beforeEach(() => { + vm = new SidebarComponent({ + propsData: { + job, + isLoading: false, + }, + }).$mount(); + }); + + describe('actions', () => { + it('should render link to new issue', () => { + expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(job.new_issue_path); + expect(vm.$el.querySelector('.js-new-issue').textContent.trim()).toEqual('New issue'); + }); + + it('should render link to retry job', () => { + expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path); + }); + + it('should render link to cancel job', () => { + expect(vm.$el.querySelector('.js-cancel-job').getAttribute('href')).toEqual(job.cancel_path); + }); + }); + + describe('information', () => { + it('should render merge request link', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-mr')), + ).toEqual('Merge Request: !2'); + + expect( + vm.$el.querySelector('.js-job-mr a').getAttribute('href'), + ).toEqual(job.merge_request.path); + }); + + it('should render job duration', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-duration')), + ).toEqual('Duration: 6 seconds'); + }); + + it('should render erased date', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-erased')), + ).toEqual('Erased: 3 weeks ago'); + }); + + it('should render finished date', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-finished')), + ).toEqual('Finished: 3 weeks ago'); + }); + + it('should render queued date', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-queued')), + ).toEqual('Queued: 9 seconds'); + }); + + it('should render runner ID', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-runner')), + ).toEqual('Runner: #1'); + }); + + it('should render coverage', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-coverage')), + ).toEqual('Coverage: 20%'); + }); + + it('should render tags', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-tags')), + ).toEqual('Tags: tag'); + }); + }); +}); diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js index 601eebce38a..f1697840fcd 100644 --- a/spec/javascripts/pipelines/nav_controls_spec.js +++ b/spec/javascripts/pipelines/nav_controls_spec.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import navControlsComp from '~/pipelines/components/nav_controls'; +import navControlsComp from '~/pipelines/components/nav_controls.vue'; describe('Pipelines Nav Controls', () => { let NavControlsComponent; diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index 2b51c89f311..e28639f12f3 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -43,6 +43,7 @@ describe('Header CI Component', () => { isLoading: false, }, ], + hasSidebarButton: true, }; vm = new HeaderCi({ @@ -90,4 +91,8 @@ describe('Header CI Component', () => { done(); }); }); + + it('should render sidebar toggle button', () => { + expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); + }); }); diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb index 27684882435..787c2372c5b 100644 --- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb @@ -47,16 +47,7 @@ describe Banzai::Filter::AbstractReferenceFilter do end end - context 'with RequestStore enabled' do - before do - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! - end - + context 'with RequestStore enabled', :request_store do it 'returns a list of Projects for a list of paths' do expect(filter.find_projects_for_paths([project.path_with_namespace])). to eq([project]) diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb index e5d332efb08..866297f94a9 100644 --- a/spec/lib/banzai/issuable_extractor_spec.rb +++ b/spec/lib/banzai/issuable_extractor_spec.rb @@ -29,16 +29,7 @@ describe Banzai::IssuableExtractor, lib: true do expect(result).to eq(issue_link => issue, merge_request_link => merge_request) end - describe 'caching' do - before do - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! - end - + describe 'caching', :request_store do it 'saves records to cache' do extractor.extract([issue_link, merge_request_link]) diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb index 592ed0d2b98..4d560667342 100644 --- a/spec/lib/banzai/reference_parser/user_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb @@ -43,18 +43,9 @@ describe Banzai::ReferenceParser::UserParser, lib: true do expect(subject.referenced_by([link])).to eq([user]) end - context 'when RequestStore is active' do + context 'when RequestStore is active', :request_store do let(:other_user) { create(:user) } - before do - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! - end - it 'does not return users from the first call in the second' do link['data-user'] = user.id.to_s diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb new file mode 100644 index 00000000000..f2073b9bcb3 --- /dev/null +++ b/spec/lib/gitlab/background_migration_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration do + describe '.steal' do + it 'steals jobs from a queue' do + queue = [double(:job, args: ['Foo', [10, 20]])] + + allow(Sidekiq::Queue).to receive(:new). + with(BackgroundMigrationWorker.sidekiq_options['queue']). + and_return(queue) + + expect(queue[0]).to receive(:delete) + + expect(described_class).to receive(:perform).with('Foo', [10, 20]) + + described_class.steal('Foo') + end + + it 'does not steal jobs for a different migration' do + queue = [double(:job, args: ['Foo', [10, 20]])] + + allow(Sidekiq::Queue).to receive(:new). + with(BackgroundMigrationWorker.sidekiq_options['queue']). + and_return(queue) + + expect(described_class).not_to receive(:perform) + + expect(queue[0]).not_to receive(:delete) + + described_class.steal('Bar') + end + end + + describe '.perform' do + it 'performs a background migration' do + instance = double(:instance) + klass = double(:klass, new: instance) + + expect(described_class).to receive(:const_get). + with('Foo'). + and_return(klass) + + expect(instance).to receive(:perform).with(10, 20) + + described_class.perform('Foo', [10, 20]) + end + end +end diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 0e10d91836d..65f05121b40 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -122,16 +122,7 @@ describe Group, 'Routable' do it { expect(group.full_path).to eq(group.path) } it { expect(nested_group.full_path).to eq("#{group.full_path}/#{nested_group.path}") } - context 'with RequestStore active' do - before do - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! - end - + context 'with RequestStore active', :request_store do it 'does not load the route table more than once' do expect(group).to receive(:uncached_full_path).once.and_call_original diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 362565506e5..497e3cdf415 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -389,16 +389,7 @@ describe ProjectTeam, models: true do end describe '#max_member_access_for_user_ids' do - context 'with RequestStore enabled' do - before do - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! - end - + context 'with RequestStore enabled', :request_store do include_examples 'max member access for users' def access_levels(users) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 86c57204971..3e831373514 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -398,6 +398,15 @@ describe API::Projects do expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond]) end + it 'uploads avatar for project a project' do + project = attributes_for(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif')) + + post api('/projects', user), project + + project_id = json_response['id'] + expect(json_response['avatar_url']).to eq("http://localhost/uploads/system/project/avatar/#{project_id}/banana_sample.gif") + end + it 'sets a project as allowing merge even if build fails' do project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false }) post api('/projects', user), project diff --git a/spec/serializers/build_entity_spec.rb b/spec/serializers/build_entity_spec.rb index 46d43a80ef7..e51ff9fc709 100644 --- a/spec/serializers/build_entity_spec.rb +++ b/spec/serializers/build_entity_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' describe BuildEntity do let(:user) { create(:user) } - let(:build) { create(:ci_build, :failed) } + let(:build) { create(:ci_build) } let(:project) { build.project } let(:request) { double('request') } before do allow(request).to receive(:current_user).and_return(user) + project.add_developer(user) end let(:entity) do @@ -16,9 +17,8 @@ describe BuildEntity do subject { entity.as_json } - it 'contains paths to build page and retry action' do - expect(subject).to include(:build_path, :retry_path) - expect(subject[:retry_path]).not_to be_nil + it 'contains paths to build page action' do + expect(subject).to include(:build_path) end it 'does not contain sensitive information' do @@ -39,12 +39,32 @@ describe BuildEntity do expect(subject[:status]).to include :icon, :favicon, :text, :label end - context 'when build is a regular job' do + context 'when build is retryable' do + before do + build.update(status: :failed) + end + + it 'contains cancel path' do + expect(subject).to include(:retry_path) + end + end + + context 'when build is cancelable' do + before do + build.update(status: :running) + end + + it 'contains cancel path' do + expect(subject).to include(:cancel_path) + end + end + + context 'when build is a regular build' do it 'does not contain path to play action' do expect(subject).not_to include(:play_path) end - it 'is not a playable job' do + it 'is not a playable build' do expect(subject[:playable]).to be false end end diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb index 088f24eb180..34b19fb9fc4 100644 --- a/spec/serializers/pipeline_serializer_spec.rb +++ b/spec/serializers/pipeline_serializer_spec.rb @@ -102,18 +102,11 @@ describe PipelineSerializer do Ci::Pipeline::AVAILABLE_STATUSES.each do |status| create_pipeline(status) end - - RequestStore.begin! - end - - after do - RequestStore.end! - RequestStore.clear! end - it "verifies number of queries" do + it 'verifies number of queries', :request_store do recorded = ActiveRecord::QueryRecorder.new { subject } - expect(recorded.count).to be_within(1).of(60) + expect(recorded.count).to be_within(1).of(57) expect(recorded.cached_count).to eq(0) end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8b8fbf6e862..1979347a178 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -74,10 +74,18 @@ RSpec.configure do |config| TestEnv.cleanup end + config.before(:example, :request_store) do + RequestStore.begin! + end + + config.after(:example, :request_store) do + RequestStore.end! + RequestStore.clear! + end + if ENV['CI'] - # Retry only on feature specs that use JS - config.around :each, :js do |ex| - ex.run_with_retry retry: 3 + config.around(:each) do |ex| + ex.run_with_retry retry: 2 end end diff --git a/spec/views/projects/jobs/show.html.haml_spec.rb b/spec/views/projects/jobs/show.html.haml_spec.rb index 8f2822f5dc5..d9a7ba265f8 100644 --- a/spec/views/projects/jobs/show.html.haml_spec.rb +++ b/spec/views/projects/jobs/show.html.haml_spec.rb @@ -15,36 +15,6 @@ describe 'projects/jobs/show', :view do allow(view).to receive(:can?).and_return(true) end - describe 'job information in header' do - let(:build) do - create(:ci_build, :success, environment: 'staging') - end - - before do - render - end - - it 'shows status name' do - expect(rendered).to have_css('.ci-status.ci-success', text: 'passed') - end - - it 'does not render a link to the job' do - expect(rendered).not_to have_link('passed') - end - - it 'shows job id' do - expect(rendered).to have_css('.js-build-id', text: build.id) - end - - it 'shows a link to the pipeline' do - expect(rendered).to have_link(build.pipeline.id) - end - - it 'shows a link to the commit' do - expect(rendered).to have_link(build.pipeline.short_sha) - end - end - describe 'environment info in job view' do context 'job with latest deployment' do let(:build) do @@ -215,34 +185,6 @@ describe 'projects/jobs/show', :view do end end - context 'when job is not running' do - before do - build.success! - render - end - - it 'shows retry button' do - expect(rendered).to have_link('Retry') - end - - context 'if build passed' do - it 'does not show New issue button' do - expect(rendered).not_to have_link('New issue') - end - end - - context 'if build failed' do - before do - build.status = 'failed' - render - end - - it 'shows New issue button' do - expect(rendered).to have_link('New issue') - end - end - end - describe 'commit title in sidebar' do let(:commit_title) { project.commit.title } @@ -269,25 +211,4 @@ describe 'projects/jobs/show', :view do expect(rendered).to have_css('.js-build-value', visible: false, text: 'TRIGGER_VALUE_2') end end - - describe 'New issue button' do - before do - build.status = 'failed' - render - end - - it 'links to issues/new with the title and description filled in' do - title = "Build Failed ##{build.id}" - build_url = namespace_project_job_url(project.namespace, project, build) - href = new_namespace_project_issue_path( - project.namespace, - project, - issue: { - title: title, - description: build_url - } - ) - expect(rendered).to have_link('New issue', href: href) - end - end end diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb new file mode 100644 index 00000000000..0d742ae9dc7 --- /dev/null +++ b/spec/workers/background_migration_worker_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe BackgroundMigrationWorker do + describe '.perform' do + it 'performs a background migration' do + expect(Gitlab::BackgroundMigration). + to receive(:perform). + with('Foo', [10, 20]) + + described_class.new.perform('Foo', [10, 20]) + end + end +end |