diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-24 21:07:55 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-24 21:07:55 +0300 |
commit | 603c7d4cac5e28bc1c75e50c23ed2cbe56f1aafc (patch) | |
tree | 907f5b8ee1b6f5aad396e95e3327a08400b9e8ea /spec | |
parent | 120f4aaedc8fe830a3f572491d240d8ee6addefb (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
24 files changed, 664 insertions, 166 deletions
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index 35801643181..5dde0d57293 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -10,21 +10,14 @@ describe Admin::ServicesController do end describe 'GET #edit' do - let!(:project) { create(:project) } - - Service.available_services_names.each do |service_name| - context "#{service_name}" do - let!(:service) do - service_template = "#{service_name}_service".camelize.constantize - service_template.where(template: true).first_or_create - end + let!(:service) do + create(:jira_service, :template) + end - it 'successfully displays the template' do - get :edit, params: { id: service.id } + it 'successfully displays the template' do + get :edit, params: { id: service.id } - expect(response).to have_gitlab_http_status(:ok) - end - end + expect(response).to have_gitlab_http_status(:ok) end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 225538dcc45..9fdaa728fd7 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -8,18 +8,17 @@ describe Projects::BlobController do let(:project) { create(:project, :public, :repository) } describe "GET show" do + def request + get(:show, params: { namespace_id: project.namespace, project_id: project, id: id }) + end + render_views context 'with file path' do before do expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original - get(:show, - params: { - namespace_id: project.namespace, - project_id: project, - id: id - }) + request end context "valid branch, valid file" do @@ -119,6 +118,32 @@ describe Projects::BlobController do end end end + + context 'when there is an artifact with code navigation data' do + let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) } + let!(:job) { create(:ci_build, pipeline: pipeline, name: Ci::Build::CODE_NAVIGATION_JOB_NAME) } + let!(:artifact) { create(:ci_job_artifact, :lsif, job: job) } + + let(:id) { 'master/README.md' } + + it 'assigns code_navigation_build variable' do + request + + expect(assigns[:code_navigation_build]).to eq(job) + end + + context 'when code_navigation feature is disabled' do + before do + stub_feature_flags(code_navigation: false) + end + + it 'does not assign code_navigation_build variable' do + request + + expect(assigns[:code_navigation_build]).to be_nil + end + end + end end describe 'GET diff' do diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb index cbc21dd02e5..d8bb4902087 100644 --- a/spec/features/milestones/user_views_milestone_spec.rb +++ b/spec/features/milestones/user_views_milestone_spec.rb @@ -25,6 +25,37 @@ describe "User views milestone" do expect { visit_milestone }.not_to exceed_query_limit(control) end + context 'limiting milestone issues' do + before_all do + 2.times do + create(:issue, milestone: milestone, project: project) + create(:issue, milestone: milestone, project: project, assignees: [user]) + create(:issue, milestone: milestone, project: project, state: :closed) + end + end + + context 'when issues on milestone are over DISPLAY_ISSUES_LIMIT' do + it "limits issues to display and shows warning" do + stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 3) + + visit(project_milestone_path(project, milestone)) + + expect(page).to have_selector('.issuable-row', count: 3) + expect(page).to have_selector('#milestone-issue-count-warning', text: 'Showing 3 of 6 issues. View all issues') + expect(page).to have_link('View all issues', href: project_issues_path(project, { milestone_title: milestone.title })) + end + end + + context 'when issues on milestone are below DISPLAY_ISSUES_LIMIT' do + it 'does not display warning' do + visit(project_milestone_path(project, milestone)) + + expect(page).not_to have_selector('#milestone-issue-count-warning', text: 'Showing 3 of 6 issues. View all issues') + expect(page).to have_selector('.issuable-row', count: 6) + end + end + end + private def visit_milestone diff --git a/spec/features/projects/environments_pod_logs_spec.rb b/spec/features/projects/environments_pod_logs_spec.rb index 121a8e1705b..2b2327940a5 100644 --- a/spec/features/projects/environments_pod_logs_spec.rb +++ b/spec/features/projects/environments_pod_logs_spec.rb @@ -29,7 +29,7 @@ describe 'Environment > Pod Logs', :js do wait_for_requests page.within('.js-environments-dropdown') do - toggle = find(".dropdown-menu-toggle:not([disabled])") + toggle = find(".dropdown-toggle:not([disabled])") expect(toggle).to have_content(environment.name) @@ -47,8 +47,8 @@ describe 'Environment > Pod Logs', :js do wait_for_requests - page.within('.js-pods-dropdown') do - find(".dropdown-menu-toggle:not([disabled])").click + page.within('.qa-pods-dropdown') do + find(".dropdown-toggle:not([disabled])").click dropdown_items = find(".dropdown-menu").all(".dropdown-item:not([disabled])") expect(dropdown_items.size).to eq(1) diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js index cfdc0dcc6cc..d5693cc4173 100644 --- a/spec/frontend/code_navigation/components/app_spec.js +++ b/spec/frontend/code_navigation/components/app_spec.js @@ -16,6 +16,7 @@ function factory(initialState = {}) { state: { ...createState(), ...initialState, + definitionPathPrefix: 'https://test.com/blob/master', }, actions: { fetchData, diff --git a/spec/frontend/code_navigation/components/popover_spec.js b/spec/frontend/code_navigation/components/popover_spec.js index ad05504a224..df3bbc7c1c6 100644 --- a/spec/frontend/code_navigation/components/popover_spec.js +++ b/spec/frontend/code_navigation/components/popover_spec.js @@ -1,6 +1,8 @@ import { shallowMount } from '@vue/test-utils'; import Popover from '~/code_navigation/components/popover.vue'; +const DEFINITION_PATH_PREFIX = 'http:/'; + const MOCK_CODE_DATA = Object.freeze({ hover: [ { @@ -8,7 +10,7 @@ const MOCK_CODE_DATA = Object.freeze({ value: 'console.log', }, ], - definition_url: 'http://test.com', + definition_path: 'test.com', }); const MOCK_DOCS_DATA = Object.freeze({ @@ -18,13 +20,13 @@ const MOCK_DOCS_DATA = Object.freeze({ value: 'console.log', }, ], - definition_url: 'http://test.com', + definition_path: 'test.com', }); let wrapper; -function factory(position, data) { - wrapper = shallowMount(Popover, { propsData: { position, data } }); +function factory(position, data, definitionPathPrefix) { + wrapper = shallowMount(Popover, { propsData: { position, data, definitionPathPrefix } }); } describe('Code navigation popover component', () => { @@ -33,14 +35,14 @@ describe('Code navigation popover component', () => { }); it('renders popover', () => { - factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA); + factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA, DEFINITION_PATH_PREFIX); expect(wrapper.element).toMatchSnapshot(); }); describe('code output', () => { it('renders code output', () => { - factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA); + factory({ x: 0, y: 0, height: 0 }, MOCK_CODE_DATA, DEFINITION_PATH_PREFIX); expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(true); expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(false); @@ -49,7 +51,7 @@ describe('Code navigation popover component', () => { describe('documentation output', () => { it('renders code output', () => { - factory({ x: 0, y: 0, height: 0 }, MOCK_DOCS_DATA); + factory({ x: 0, y: 0, height: 0 }, MOCK_DOCS_DATA, DEFINITION_PATH_PREFIX); expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(false); expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(true); diff --git a/spec/frontend/code_navigation/store/actions_spec.js b/spec/frontend/code_navigation/store/actions_spec.js index 9c44480ca67..f58dc283ada 100644 --- a/spec/frontend/code_navigation/store/actions_spec.js +++ b/spec/frontend/code_navigation/store/actions_spec.js @@ -27,12 +27,10 @@ describe('Code navigation actions', () => { describe('fetchData', () => { let mock; - const state = { - projectPath: 'gitlab-org/gitlab', - commitId: '123', - blobPath: 'index', - }; - const apiUrl = '/api/1/projects/gitlab-org%2Fgitlab/commits/123/lsif/info'; + + const codeNavUrl = + 'gitlab-org/gitlab-shell/-/jobs/1114/artifacts/raw/lsif/cmd/check/main.go.json'; + const state = { codeNavUrl }; beforeEach(() => { window.gon = { api_version: '1' }; @@ -45,20 +43,18 @@ describe('Code navigation actions', () => { describe('success', () => { beforeEach(() => { - mock.onGet(apiUrl).replyOnce(200, { - index: [ - { - start_line: 0, - start_char: 0, - hover: { value: '123' }, - }, - { - start_line: 1, - start_char: 0, - hover: null, - }, - ], - }); + mock.onGet(codeNavUrl).replyOnce(200, [ + { + start_line: 0, + start_char: 0, + hover: { value: '123' }, + }, + { + start_line: 1, + start_char: 0, + hover: null, + }, + ]); }); it('commits REQUEST_DATA_SUCCESS with normalized data', done => { @@ -106,7 +102,7 @@ describe('Code navigation actions', () => { describe('error', () => { beforeEach(() => { - mock.onGet(apiUrl).replyOnce(500); + mock.onGet(codeNavUrl).replyOnce(500); }); it('dispatches requestDataError', done => { diff --git a/spec/frontend/code_navigation/store/mutations_spec.js b/spec/frontend/code_navigation/store/mutations_spec.js index 117a2ed2f14..305386f4d0b 100644 --- a/spec/frontend/code_navigation/store/mutations_spec.js +++ b/spec/frontend/code_navigation/store/mutations_spec.js @@ -11,14 +11,12 @@ describe('Code navigation mutations', () => { describe('SET_INITIAL_DATA', () => { it('sets initial data', () => { mutations.SET_INITIAL_DATA(state, { - projectPath: 'test', - commitId: '123', - blobPath: 'index.js', + codeNavUrl: 'https://test.com/builds/1005', + definitionPathPrefix: 'https://test.com/blob/master', }); - expect(state.projectPath).toBe('test'); - expect(state.commitId).toBe('123'); - expect(state.blobPath).toBe('index.js'); + expect(state.codeNavUrl).toBe('https://test.com/builds/1005'); + expect(state.definitionPathPrefix).toBe('https://test.com/blob/master'); }); }); diff --git a/spec/frontend/ide/components/branches/search_list_spec.js b/spec/frontend/ide/components/branches/search_list_spec.js index fe142d70698..826d51b24f1 100644 --- a/spec/frontend/ide/components/branches/search_list_spec.js +++ b/spec/frontend/ide/components/branches/search_list_spec.js @@ -9,6 +9,8 @@ import { branches } from '../../mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); +jest.mock('lodash/debounce', () => jest.fn); + describe('IDE branches search list', () => { let wrapper; const fetchBranchesMock = jest.fn(); diff --git a/spec/frontend/logs/components/environment_logs_spec.js b/spec/frontend/logs/components/environment_logs_spec.js index 49642153c69..4da987725a1 100644 --- a/spec/frontend/logs/components/environment_logs_spec.js +++ b/spec/frontend/logs/components/environment_logs_spec.js @@ -1,7 +1,5 @@ -import Vue from 'vue'; -import { GlSprintf, GlIcon, GlDropdown, GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui'; +import { GlSprintf, GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import EnvironmentLogs from '~/logs/components/environment_logs.vue'; import { createStore } from '~/logs/stores'; @@ -13,7 +11,6 @@ import { mockLogsResult, mockTrace, mockPodName, - mockSearch, mockEnvironmentsEndpoint, mockDocumentationPath, } from '../mock_data'; @@ -29,7 +26,6 @@ jest.mock('lodash/throttle', () => ); describe('EnvironmentLogs', () => { - let EnvironmentLogsComponent; let store; let dispatch; let wrapper; @@ -44,13 +40,9 @@ describe('EnvironmentLogs', () => { const updateControlBtnsMock = jest.fn(); const findEnvironmentsDropdown = () => wrapper.find('.js-environments-dropdown'); - const findPodsDropdown = () => wrapper.find('.js-pods-dropdown'); - const findPodsDropdownItems = () => - findPodsDropdown() - .findAll(GlDropdownItem) - .filter(itm => !itm.attributes('disabled')); - const findSearchBar = () => wrapper.find('.js-logs-search'); - const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' }); + + const findSimpleFilters = () => wrapper.find({ ref: 'log-simple-filters' }); + const findAdvancedFilters = () => wrapper.find({ ref: 'log-advanced-filters' }); const findInfoAlert = () => wrapper.find('.js-elasticsearch-alert'); const findLogControlButtons = () => wrapper.find({ name: 'log-control-buttons-stub' }); @@ -79,7 +71,7 @@ describe('EnvironmentLogs', () => { }; const initWrapper = () => { - wrapper = shallowMount(EnvironmentLogsComponent, { + wrapper = shallowMount(EnvironmentLogs, { propsData, store, stubs: { @@ -111,7 +103,6 @@ describe('EnvironmentLogs', () => { beforeEach(() => { store = createStore(); state = store.state.environmentLogs; - EnvironmentLogsComponent = Vue.extend(EnvironmentLogs); jest.spyOn(store, 'dispatch').mockResolvedValue(); @@ -132,17 +123,10 @@ describe('EnvironmentLogs', () => { expect(wrapper.isVueInstance()).toBe(true); expect(wrapper.isEmpty()).toBe(false); - // top bar expect(findEnvironmentsDropdown().is(GlDropdown)).toBe(true); - expect(findPodsDropdown().is(GlDropdown)).toBe(true); + expect(findSimpleFilters().exists()).toBe(true); expect(findLogControlButtons().exists()).toBe(true); - expect(findSearchBar().exists()).toBe(true); - expect(findSearchBar().is(GlSearchBoxByClick)).toBe(true); - expect(findTimeRangePicker().exists()).toBe(true); - expect(findTimeRangePicker().is(DateTimePicker)).toBe(true); - - // log trace expect(findInfiniteScroll().exists()).toBe(true); expect(findLogTrace().exists()).toBe(true); }); @@ -181,20 +165,6 @@ describe('EnvironmentLogs', () => { expect(findEnvironmentsDropdown().findAll(GlDropdownItem).length).toBe(0); }); - it('displays a disabled pods dropdown', () => { - expect(findPodsDropdown().attributes('disabled')).toBe('true'); - expect(findPodsDropdownItems()).toHaveLength(0); - }); - - it('displays a disabled search bar', () => { - expect(findSearchBar().exists()).toBe(true); - expect(findSearchBar().attributes('disabled')).toBe('true'); - }); - - it('displays a disabled time window dropdown', () => { - expect(findTimeRangePicker().attributes('disabled')).toBe('true'); - }); - it('does not update buttons state', () => { expect(updateControlBtnsMock).not.toHaveBeenCalled(); }); @@ -237,17 +207,14 @@ describe('EnvironmentLogs', () => { initWrapper(); }); - it('displays a disabled time window dropdown', () => { - expect(findTimeRangePicker().attributes('disabled')).toBe('true'); - }); - - it('displays a disabled search bar', () => { - expect(findSearchBar().attributes('disabled')).toBe('true'); - }); - it('displays an alert to upgrade to ES', () => { expect(findInfoAlert().exists()).toBe(true); }); + + it('displays simple filters for kubernetes logs API', () => { + expect(findSimpleFilters().exists()).toBe(true); + expect(findAdvancedFilters().exists()).toBe(false); + }); }); describe('state with data', () => { @@ -271,21 +238,6 @@ describe('EnvironmentLogs', () => { updateControlBtnsMock.mockReset(); }); - it('displays an enabled search bar', () => { - expect(findSearchBar().attributes('disabled')).toBeFalsy(); - - // input a query and click `search` - findSearchBar().vm.$emit('input', mockSearch); - findSearchBar().vm.$emit('submit'); - - expect(dispatch).toHaveBeenCalledWith(`${module}/setInitData`, expect.any(Object)); - expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, mockSearch); - }); - - it('displays an enabled time window dropdown', () => { - expect(findTimeRangePicker().attributes('disabled')).toBeFalsy(); - }); - it('does not display an alert to upgrade to ES', () => { expect(findInfoAlert().exists()).toBe(false); }); @@ -306,24 +258,16 @@ describe('EnvironmentLogs', () => { const item = items.at(i); if (item.text() !== mockEnvName) { - expect(item.find(GlIcon).classes()).toContain('invisible'); + expect(item.find(GlIcon).classes('invisible')).toBe(true); } else { - // selected - expect(item.find(GlIcon).classes()).not.toContain('invisible'); + expect(item.find(GlIcon).classes('invisible')).toBe(false); } }); }); - it('populates pods dropdown', () => { - const items = findPodsDropdownItems(); - - expect(findPodsDropdown().props('text')).toBe(mockPodName); - expect(items.length).toBe(mockPods.length + 1); - expect(items.at(0).text()).toBe('All pods'); - mockPods.forEach((pod, i) => { - const item = items.at(i + 1); - expect(item.text()).toBe(pod); - }); + it('displays advanced filters for elasticsearch logs API', () => { + expect(findSimpleFilters().exists()).toBe(false); + expect(findAdvancedFilters().exists()).toBe(true); }); it('shows infinite scroll with height and no content', () => { @@ -331,19 +275,6 @@ describe('EnvironmentLogs', () => { expect(getInfiniteScrollAttr('fetched-items')).toBe(mockTrace.length); }); - it('dropdown has one pod selected', () => { - const items = findPodsDropdownItems(); - mockPods.forEach((pod, i) => { - const item = items.at(i); - if (item.text() !== mockPodName) { - expect(item.find(GlIcon).classes()).toContain('invisible'); - } else { - // selected - expect(item.find(GlIcon).classes()).not.toContain('invisible'); - } - }); - }); - it('populates logs trace', () => { const trace = findLogTrace(); expect(trace.text().split('\n').length).toBe(mockTrace.length); @@ -371,17 +302,6 @@ describe('EnvironmentLogs', () => { ); }); - it('pod name, trace is refreshed', () => { - const items = findPodsDropdownItems(); - const index = 2; // any pod - - expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); - - items.at(index + 1).vm.$emit('click'); - - expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]); - }); - it('refresh button, trace is refreshed', () => { expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); diff --git a/spec/frontend/logs/components/log_advanced_filters_spec.js b/spec/frontend/logs/components/log_advanced_filters_spec.js new file mode 100644 index 00000000000..a6fbc40c2c6 --- /dev/null +++ b/spec/frontend/logs/components/log_advanced_filters_spec.js @@ -0,0 +1,185 @@ +import { GlIcon, GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { defaultTimeRange } from '~/vue_shared/constants'; +import { convertToFixedRange } from '~/lib/utils/datetime_range'; +import { createStore } from '~/logs/stores'; +import { mockPods, mockSearch } from '../mock_data'; + +import LogAdvancedFilters from '~/logs/components/log_advanced_filters.vue'; + +const module = 'environmentLogs'; + +describe('LogAdvancedFilters', () => { + let store; + let dispatch; + let wrapper; + let state; + + const findPodsDropdown = () => wrapper.find({ ref: 'podsDropdown' }); + const findPodsNoPodsText = () => wrapper.find({ ref: 'noPodsMsg' }); + const findPodsDropdownItems = () => + findPodsDropdown() + .findAll(GlDropdownItem) + .filter(item => !item.is('[disabled]')); + const findPodsDropdownItemsSelected = () => + findPodsDropdownItems() + .filter(item => { + return !item.find(GlIcon).classes('invisible'); + }) + .at(0); + const findSearchBox = () => wrapper.find({ ref: 'searchBox' }); + const findTimeRangePicker = () => wrapper.find({ ref: 'dateTimePicker' }); + + const mockStateLoading = () => { + state.timeRange.selected = defaultTimeRange; + state.timeRange.current = convertToFixedRange(defaultTimeRange); + state.pods.options = []; + state.pods.current = null; + }; + + const mockStateWithData = () => { + state.timeRange.selected = defaultTimeRange; + state.timeRange.current = convertToFixedRange(defaultTimeRange); + state.pods.options = mockPods; + state.pods.current = null; + }; + + const initWrapper = (propsData = {}) => { + wrapper = shallowMount(LogAdvancedFilters, { + propsData: { + ...propsData, + }, + store, + }); + }; + + beforeEach(() => { + store = createStore(); + state = store.state.environmentLogs; + + jest.spyOn(store, 'dispatch').mockResolvedValue(); + + dispatch = store.dispatch; + }); + + afterEach(() => { + store.dispatch.mockReset(); + + if (wrapper) { + wrapper.destroy(); + } + }); + + it('displays UI elements', () => { + initWrapper(); + + expect(wrapper.isVueInstance()).toBe(true); + expect(wrapper.isEmpty()).toBe(false); + + expect(findPodsDropdown().exists()).toBe(true); + expect(findSearchBox().exists()).toBe(true); + expect(findTimeRangePicker().exists()).toBe(true); + }); + + describe('disabled state', () => { + beforeEach(() => { + mockStateLoading(); + initWrapper({ + disabled: true, + }); + }); + + it('displays disabled filters', () => { + expect(findPodsDropdown().props('text')).toBe('All pods'); + expect(findPodsDropdown().attributes('disabled')).toBeTruthy(); + expect(findSearchBox().attributes('disabled')).toBeTruthy(); + expect(findTimeRangePicker().attributes('disabled')).toBeTruthy(); + }); + }); + + describe('when the state is loading', () => { + beforeEach(() => { + mockStateLoading(); + initWrapper(); + }); + + it('displays a enabled filters', () => { + expect(findPodsDropdown().props('text')).toBe('All pods'); + expect(findPodsDropdown().attributes('disabled')).toBeFalsy(); + expect(findSearchBox().attributes('disabled')).toBeFalsy(); + expect(findTimeRangePicker().attributes('disabled')).toBeFalsy(); + }); + + it('displays an empty pods dropdown', () => { + expect(findPodsNoPodsText().exists()).toBe(true); + expect(findPodsDropdownItems()).toHaveLength(0); + }); + }); + + describe('when the state has data', () => { + beforeEach(() => { + mockStateWithData(); + initWrapper(); + }); + + it('displays an enabled pods dropdown', () => { + expect(findPodsDropdown().attributes('disabled')).toBeFalsy(); + expect(findPodsDropdown().props('text')).toBe('All pods'); + }); + + it('displays options in a pods dropdown', () => { + const items = findPodsDropdownItems(); + expect(items).toHaveLength(mockPods.length + 1); + }); + + it('displays "all pods" selected in a pods dropdown', () => { + const selected = findPodsDropdownItemsSelected(); + + expect(selected.text()).toBe('All pods'); + }); + + it('displays options in date time picker', () => { + const options = findTimeRangePicker().props('options'); + + expect(options).toEqual(expect.any(Array)); + expect(options.length).toBeGreaterThan(0); + }); + + describe('when the user interacts', () => { + it('clicks on a all options, showPodLogs is dispatched with null', () => { + const items = findPodsDropdownItems(); + items.at(0).vm.$emit('click'); + + expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, null); + }); + + it('clicks on a pod name, showPodLogs is dispatched with pod name', () => { + const items = findPodsDropdownItems(); + const index = 2; // any pod + + items.at(index + 1).vm.$emit('click'); // skip "All pods" option + + expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]); + }); + + it('clicks on search, a serches is done', () => { + expect(findSearchBox().attributes('disabled')).toBeFalsy(); + + // input a query and click `search` + findSearchBox().vm.$emit('input', mockSearch); + findSearchBox().vm.$emit('submit'); + + expect(dispatch).toHaveBeenCalledWith(`${module}/setSearch`, mockSearch); + }); + + it('selects a new time range', () => { + expect(findTimeRangePicker().attributes('disabled')).toBeFalsy(); + + const mockRange = { start: 'START_DATE', end: 'END_DATE' }; + findTimeRangePicker().vm.$emit('input', mockRange); + + expect(dispatch).toHaveBeenCalledWith(`${module}/setTimeRange`, mockRange); + }); + }); + }); +}); diff --git a/spec/frontend/logs/components/log_simple_filters_spec.js b/spec/frontend/logs/components/log_simple_filters_spec.js new file mode 100644 index 00000000000..13504a2b1fc --- /dev/null +++ b/spec/frontend/logs/components/log_simple_filters_spec.js @@ -0,0 +1,138 @@ +import { GlIcon, GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { createStore } from '~/logs/stores'; +import { mockPods, mockPodName } from '../mock_data'; + +import LogSimpleFilters from '~/logs/components/log_simple_filters.vue'; + +const module = 'environmentLogs'; + +describe('LogSimpleFilters', () => { + let store; + let dispatch; + let wrapper; + let state; + + const findPodsDropdown = () => wrapper.find({ ref: 'podsDropdown' }); + const findPodsNoPodsText = () => wrapper.find({ ref: 'noPodsMsg' }); + const findPodsDropdownItems = () => + findPodsDropdown() + .findAll(GlDropdownItem) + .filter(item => !item.is('[disabled]')); + + const mockPodsLoading = () => { + state.pods.options = []; + state.pods.current = null; + }; + + const mockPodsLoaded = () => { + state.pods.options = mockPods; + state.pods.current = mockPodName; + }; + + const initWrapper = (propsData = {}) => { + wrapper = shallowMount(LogSimpleFilters, { + propsData: { + ...propsData, + }, + store, + }); + }; + + beforeEach(() => { + store = createStore(); + state = store.state.environmentLogs; + + jest.spyOn(store, 'dispatch').mockResolvedValue(); + + dispatch = store.dispatch; + }); + + afterEach(() => { + store.dispatch.mockReset(); + + if (wrapper) { + wrapper.destroy(); + } + }); + + it('displays UI elements', () => { + initWrapper(); + + expect(wrapper.isVueInstance()).toBe(true); + expect(wrapper.isEmpty()).toBe(false); + + expect(findPodsDropdown().exists()).toBe(true); + }); + + describe('disabled state', () => { + beforeEach(() => { + mockPodsLoading(); + initWrapper({ + disabled: true, + }); + }); + + it('displays a disabled pods dropdown', () => { + expect(findPodsDropdown().props('text')).toBe('No pod selected'); + expect(findPodsDropdown().attributes('disabled')).toBeTruthy(); + }); + }); + + describe('loading state', () => { + beforeEach(() => { + mockPodsLoading(); + initWrapper(); + }); + + it('displays an enabled pods dropdown', () => { + expect(findPodsDropdown().attributes('disabled')).toBeFalsy(); + expect(findPodsDropdown().props('text')).toBe('No pod selected'); + }); + + it('displays an empty pods dropdown', () => { + expect(findPodsNoPodsText().exists()).toBe(true); + expect(findPodsDropdownItems()).toHaveLength(0); + }); + }); + + describe('pods available state', () => { + beforeEach(() => { + mockPodsLoaded(); + initWrapper(); + }); + + it('displays an enabled pods dropdown', () => { + expect(findPodsDropdown().attributes('disabled')).toBeFalsy(); + expect(findPodsDropdown().props('text')).toBe(mockPods[0]); + }); + + it('displays a pods dropdown with items', () => { + expect(findPodsNoPodsText().exists()).toBe(false); + expect(findPodsDropdownItems()).toHaveLength(mockPods.length); + }); + + it('dropdown has one pod selected', () => { + const items = findPodsDropdownItems(); + mockPods.forEach((pod, i) => { + const item = items.at(i); + if (item.text() !== mockPodName) { + expect(item.find(GlIcon).classes('invisible')).toBe(true); + } else { + expect(item.find(GlIcon).classes('invisible')).toBe(false); + } + }); + }); + + it('when the user clicks on a pod, showPodLogs is dispatched', () => { + const items = findPodsDropdownItems(); + const index = 2; // any pod + + expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); + + items.at(index).vm.$emit('click'); + + expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]); + }); + }); +}); diff --git a/spec/frontend/logs/stores/getters_spec.js b/spec/frontend/logs/stores/getters_spec.js index fdce575fa97..9d213d8c01f 100644 --- a/spec/frontend/logs/stores/getters_spec.js +++ b/spec/frontend/logs/stores/getters_spec.js @@ -1,7 +1,7 @@ -import * as getters from '~/logs/stores/getters'; +import { trace, showAdvancedFilters } from '~/logs/stores/getters'; import logsPageState from '~/logs/stores/state'; -import { mockLogsResult, mockTrace } from '../mock_data'; +import { mockLogsResult, mockTrace, mockEnvName, mockEnvironments } from '../mock_data'; describe('Logs Store getters', () => { let state; @@ -13,7 +13,7 @@ describe('Logs Store getters', () => { describe('trace', () => { describe('when state is initialized', () => { it('returns an empty string', () => { - expect(getters.trace(state)).toEqual(''); + expect(trace(state)).toEqual(''); }); }); @@ -23,7 +23,7 @@ describe('Logs Store getters', () => { }); it('returns an empty string', () => { - expect(getters.trace(state)).toEqual(''); + expect(trace(state)).toEqual(''); }); }); @@ -33,7 +33,42 @@ describe('Logs Store getters', () => { }); it('returns an empty string', () => { - expect(getters.trace(state)).toEqual(mockTrace.join('\n')); + expect(trace(state)).toEqual(mockTrace.join('\n')); + }); + }); + }); + + describe('showAdvancedFilters', () => { + describe('when no environments are set', () => { + beforeEach(() => { + state.environments.current = mockEnvName; + state.environments.options = []; + }); + + it('returns false', () => { + expect(showAdvancedFilters(state)).toBe(false); + }); + }); + + describe('when the environment supports filters', () => { + beforeEach(() => { + state.environments.current = mockEnvName; + state.environments.options = mockEnvironments; + }); + + it('returns true', () => { + expect(showAdvancedFilters(state)).toBe(true); + }); + }); + + describe('when the environment does not support filters', () => { + beforeEach(() => { + state.environments.options = mockEnvironments; + state.environments.current = mockEnvironments[1].name; + }); + + it('returns true', () => { + expect(showAdvancedFilters(state)).toBe(false); }); }); }); diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 2c8f76c8f34..9bba3eb2b77 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -46,6 +46,7 @@ describe Gitlab::Ci::Config::Entry::Reports do :lsif | 'lsif.json' :dotenv | 'build.dotenv' :cobertura | 'cobertura-coverage.xml' + :terraform | 'tfplan.json' end with_them do diff --git a/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb new file mode 100644 index 00000000000..6f20b8877e0 --- /dev/null +++ b/spec/lib/gitlab/grape_logging/loggers/perf_logger_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::GrapeLogging::Loggers::PerfLogger do + subject { described_class.new } + + describe ".parameters" do + let(:mock_request) { OpenStruct.new(env: {}) } + + describe 'when no performance datais are present' do + it 'returns an empty Hash' do + expect(subject.parameters(mock_request, nil)).to eq({}) + end + end + + describe 'when Redis calls are present', :request_store do + it 'returns a Hash with Redis information' do + Gitlab::Redis::SharedState.with { |redis| redis.get('perf-logger-test') } + + payload = subject.parameters(mock_request, nil) + + expect(payload[:redis_calls]).to eq(1) + expect(payload[:redis_duration_ms]).to be >= 0 + end + end + end +end diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb index c2674638743..9788c9f4a3c 100644 --- a/spec/lib/gitlab/instrumentation_helper_spec.rb +++ b/spec/lib/gitlab/instrumentation_helper_spec.rb @@ -1,11 +1,51 @@ # frozen_string_literal: true -require 'fast_spec_helper' +require 'spec_helper' require 'rspec-parameterized' describe Gitlab::InstrumentationHelper do using RSpec::Parameterized::TableSyntax + describe '.add_instrumentation_data', :request_store do + let(:payload) { {} } + + subject { described_class.add_instrumentation_data(payload) } + + it 'adds nothing' do + subject + + expect(payload).to eq({}) + end + + context 'when Gitaly calls are made' do + it 'adds Gitaly data and omits Redis data' do + project = create(:project) + RequestStore.clear! + project.repository.exists? + + subject + + expect(payload[:gitaly_calls]).to eq(1) + expect(payload[:gitaly_duration]).to be >= 0 + expect(payload[:redis_calls]).to be_nil + expect(payload[:redis_duration_ms]).to be_nil + end + end + + context 'when Redis calls are made' do + it 'adds Redis data and omits Gitaly data' do + Gitlab::Redis::Cache.with { |redis| redis.get('test-instrumentation') } + + subject + + expect(payload[:redis_calls]).to eq(1) + expect(payload[:redis_duration_ms]).to be >= 0 + expect(payload[:gitaly_calls]).to be_nil + expect(payload[:gitaly_duration]).to be_nil + end + end + end + describe '.queue_duration_for_job' do where(:enqueued_at, :created_at, :time_now, :expected_duration) do "2019-06-01T00:00:00.000+0000" | nil | "2019-06-01T02:00:00.000+0000" | 2.hours.to_f diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb index bd04d30f85f..aab63ba88ad 100644 --- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb @@ -175,26 +175,30 @@ describe Gitlab::SidekiqLogging::StructuredLogger do end end - context 'with Gitaly and Rugged calls' do + context 'with Gitaly, Rugged, and Redis calls' do let(:timing_data) do { gitaly_calls: 10, gitaly_duration: 10000, rugged_calls: 1, - rugged_duration_ms: 5000 + rugged_duration_ms: 5000, + redis_calls: 3, + redis_duration_ms: 1234 } end - before do - job.merge!(timing_data) + let(:expected_end_payload) do + end_payload.except('args').merge(timing_data) end it 'logs with Gitaly and Rugged timing data' do Timecop.freeze(timestamp) do expect(logger).to receive(:info).with(start_payload.except('args')).ordered - expect(logger).to receive(:info).with(end_payload.except('args')).ordered + expect(logger).to receive(:info).with(expected_end_payload).ordered - subject.call(job, 'test_queue') { } + subject.call(job, 'test_queue') do + job.merge!(timing_data) + end end end end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index de93c3c1675..6f6ff3704b4 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -140,6 +140,18 @@ describe Ci::JobArtifact do end end + describe '.for_job_name' do + it 'returns job artifacts for a given job name' do + first_job = create(:ci_build, name: 'first') + second_job = create(:ci_build, name: 'second') + first_artifact = create(:ci_job_artifact, job: first_job) + second_artifact = create(:ci_job_artifact, job: second_job) + + expect(described_class.for_job_name(first_job.name)).to eq([first_artifact]) + expect(described_class.for_job_name(second_job.name)).to eq([second_artifact]) + end + end + describe 'callbacks' do subject { create(:ci_job_artifact, :archive) } diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index cff607a4731..5808d6e37e5 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -33,17 +33,34 @@ describe Milestone, 'Milestoneish' do end describe '#sorted_issues' do - it 'sorts issues by label priority' do + before do issue.labels << label_1 security_issue_1.labels << label_2 closed_issue_1.labels << label_3 + end + it 'sorts issues by label priority' do issues = milestone.sorted_issues(member) expect(issues.first).to eq(issue) expect(issues.second).to eq(security_issue_1) expect(issues.third).not_to eq(closed_issue_1) end + + it 'limits issue count and keeps the ordering' do + stub_const('Milestoneish::DISPLAY_ISSUES_LIMIT', 4) + + issues = milestone.sorted_issues(member) + # Cannot use issues.count here because it is sorting + # by a virtual column 'highest_priority' and it will break + # the query. + total_issues_count = issues.opened.unassigned.length + issues.opened.assigned.length + issues.closed.length + expect(issues.length).to eq(4) + expect(total_issues_count).to eq(4) + expect(issues.first).to eq(issue) + expect(issues.second).to eq(security_issue_1) + expect(issues.third).not_to eq(closed_issue_1) + end end context 'attributes visibility' do diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 7c80b5231d1..cecd4f76fc5 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -149,9 +149,58 @@ describe Service do end end - describe "Template" do + describe 'template' do let(:project) { create(:project) } + shared_examples 'retrieves service templates' do + it 'returns the available service templates' do + expect(Service.find_or_create_templates.pluck(:type)).to match_array(Service.available_services_types) + end + end + + describe '.find_or_create_templates' do + it 'creates service templates' do + expect { Service.find_or_create_templates }.to change { Service.count }.from(0).to(Service.available_services_names.size) + end + + it_behaves_like 'retrieves service templates' + + context 'with all existing templates' do + before do + Service.insert_all( + Service.available_services_types.map { |type| { template: true, type: type } } + ) + end + + it 'does not create service templates' do + expect { Service.find_or_create_templates }.to change { Service.count }.by(0) + end + + it_behaves_like 'retrieves service templates' + + context 'with a previous existing service (Previous) and a new service (Asana)' do + before do + Service.insert(type: 'PreviousService', template: true) + Service.delete_by(type: 'AsanaService', template: true) + end + + it_behaves_like 'retrieves service templates' + end + end + + context 'with a few existing templates' do + before do + create(:jira_service, :template) + end + + it 'creates the rest of the service templates' do + expect { Service.find_or_create_templates }.to change { Service.count }.from(1).to(Service.available_services_names.size) + end + + it_behaves_like 'retrieves service templates' + end + end + describe '.build_from_template' do context 'when template is invalid' do it 'sets service template to inactive when template is invalid' do diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 0ed4dcec93e..86b68dc3ade 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -34,6 +34,7 @@ describe Ci::RetryBuildService do job_artifacts_container_scanning job_artifacts_dast job_artifacts_license_management job_artifacts_license_scanning job_artifacts_performance job_artifacts_lsif + job_artifacts_terraform job_artifacts_codequality job_artifacts_metrics scheduled_at job_variables waiting_for_resource_at job_artifacts_metrics_referee job_artifacts_network_referee job_artifacts_dotenv diff --git a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb index 81ac6bd94db..e58723324d3 100644 --- a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb +++ b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb @@ -26,3 +26,15 @@ shared_examples 'accepted carrierwave upload' do expect { uploader.cache!(fixture_file) }.to change { uploader.file }.from(nil).to(kind_of(CarrierWave::SanitizedFile)) end end + +# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg') +# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload. +# @param content_type [String] the upload file content type after cache +shared_examples 'upload with content type' do |content_type| + let(:fixture_file) { fixture_file_upload(path, content_type) } + + it 'will not change upload file content type' do + uploader.cache!(fixture_file) + expect(uploader.file.content_type).to eq(content_type) + end +end diff --git a/spec/uploaders/content_type_whitelist_spec.rb b/spec/uploaders/content_type_whitelist_spec.rb index be519ead1c8..4689f83759d 100644 --- a/spec/uploaders/content_type_whitelist_spec.rb +++ b/spec/uploaders/content_type_whitelist_spec.rb @@ -18,6 +18,7 @@ describe ContentTypeWhitelist do let(:path) { File.join('spec', 'fixtures', 'rails_sample.jpg') } it_behaves_like 'accepted carrierwave upload' + it_behaves_like 'upload with content type', 'image/jpeg' end context 'upload non-whitelisted file content type' do diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb index 60b5a6697b1..a03cf3b9dea 100644 --- a/spec/uploaders/job_artifact_uploader_spec.rb +++ b/spec/uploaders/job_artifact_uploader_spec.rb @@ -97,5 +97,12 @@ describe JobArtifactUploader do it_behaves_like "migrates", to_store: described_class::Store::REMOTE it_behaves_like "migrates", from_store: described_class::Store::REMOTE, to_store: described_class::Store::LOCAL + + # CI job artifacts usually are shown as text/plain, but they contain + # escape characters so MIME detectors usually fail to determine what + # the Content-Type is. + it 'does not set Content-Type' do + expect(uploader.file.content_type).to be_blank + end end end |