diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-21 12:09:34 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-21 12:09:34 +0300 |
commit | 5bd4297fd759a14ad9ab9232cb985d28bf44ac49 (patch) | |
tree | 6c970916f6e8ec494a5631f6defe41a5cd206f93 /spec | |
parent | 51da0a2d3976111b19d2996591e08bea5111beb3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
29 files changed, 213 insertions, 1121 deletions
diff --git a/spec/frontend/ide/components/activity_bar_spec.js b/spec/frontend/ide/components/activity_bar_spec.js index 8b3853d4535..762f3c5dad1 100644 --- a/spec/frontend/ide/components/activity_bar_spec.js +++ b/spec/frontend/ide/components/activity_bar_spec.js @@ -1,15 +1,17 @@ import Vue from 'vue'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import { leftSidebarViews } from '~/ide/constants'; import ActivityBar from '~/ide/components/activity_bar.vue'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { resetStore } from '../helpers'; describe('IDE activity bar', () => { const Component = Vue.extend(ActivityBar); let vm; + let store; beforeEach(() => { + store = createStore(); + Vue.set(store.state.projects, 'abcproject', { web_url: 'testing', }); @@ -20,8 +22,6 @@ describe('IDE activity bar', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); describe('updateActivityBarView', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js index 16d0b354a30..dbb43e43c19 100644 --- a/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/empty_state_spec.js @@ -1,13 +1,15 @@ import Vue from 'vue'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import emptyState from '~/ide/components/commit_sidebar/empty_state.vue'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { resetStore } from '../../helpers'; describe('IDE commit panel empty state', () => { let vm; + let store; beforeEach(() => { + store = createStore(); + const Component = Vue.extend(emptyState); Vue.set(store.state, 'noChangesStateSvgPath', 'no-changes'); @@ -19,8 +21,6 @@ describe('IDE commit panel empty state', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders no changes text when last commit message is empty', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js index c62df4a3795..278b7bcf5aa 100644 --- a/spec/frontend/ide/components/commit_sidebar/form_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js @@ -1,19 +1,20 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { projectData } from 'jest/ide/mock_data'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import CommitForm from '~/ide/components/commit_sidebar/form.vue'; import { leftSidebarViews } from '~/ide/constants'; -import { resetStore } from '../../helpers'; import waitForPromises from 'helpers/wait_for_promises'; describe('IDE commit form', () => { const Component = Vue.extend(CommitForm); let vm; + let store; const beginCommitButton = () => vm.$el.querySelector('[data-testid="begin-commit-button"]'); beforeEach(() => { + store = createStore(); store.state.changedFiles.push('test'); store.state.currentProjectId = 'abcproject'; store.state.currentBranchId = 'master'; @@ -24,8 +25,6 @@ describe('IDE commit form', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('enables begin commit button when there are changes', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js index 45372d18965..42e0a20bc7b 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js @@ -1,14 +1,17 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; import { file } from '../../helpers'; import { removeWhitespace } from '../../../helpers/text_helper'; describe('Multi-file editor commit sidebar list collapsed', () => { let vm; + let store; beforeEach(() => { + store = createStore(); + const Component = Vue.extend(listCollapsed); vm = createComponentWithStore(Component, store, { diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js index 2b5664ffc4e..2107ff96e95 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js @@ -1,13 +1,16 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import commitSidebarList from '~/ide/components/commit_sidebar/list.vue'; -import { file, resetStore } from '../../helpers'; +import { file } from '../../helpers'; describe('Multi-file editor commit sidebar list', () => { + let store; let vm; beforeEach(() => { + store = createStore(); + const Component = Vue.extend(commitSidebarList); vm = createComponentWithStore(Component, store, { @@ -26,8 +29,6 @@ describe('Multi-file editor commit sidebar list', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); describe('with a list of files', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js index ac80ba58056..bf61f4bbe77 100644 --- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js @@ -1,13 +1,15 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { resetStore } from 'jest/ide/helpers'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue'; describe('IDE commit sidebar radio group', () => { let vm; + let store; beforeEach(done => { + store = createStore(); + const Component = Vue.extend(radioGroup); store.state.commit.commitAction = '2'; @@ -25,8 +27,6 @@ describe('IDE commit sidebar radio group', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('uses label if present', () => { diff --git a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js index e1a432b81be..db13c90fbb9 100644 --- a/spec/frontend/ide/components/commit_sidebar/success_message_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/success_message_spec.js @@ -1,13 +1,15 @@ import Vue from 'vue'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import successMessage from '~/ide/components/commit_sidebar/success_message.vue'; import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper'; -import { resetStore } from '../../helpers'; describe('IDE commit panel successful commit state', () => { let vm; + let store; beforeEach(() => { + store = createStore(); + const Component = Vue.extend(successMessage); vm = createComponentWithStore(Component, store, { @@ -19,8 +21,6 @@ describe('IDE commit panel successful commit state', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders last commit message when it exists', done => { diff --git a/spec/frontend/ide/components/file_row_extra_spec.js b/spec/frontend/ide/components/file_row_extra_spec.js index e78bacadebb..4bd27d23f76 100644 --- a/spec/frontend/ide/components/file_row_extra_spec.js +++ b/spec/frontend/ide/components/file_row_extra_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createStore } from '~/ide/stores'; import FileRowExtra from '~/ide/components/file_row_extra.vue'; -import { file, resetStore } from '../helpers'; +import { file } from '../helpers'; describe('IDE extra file row component', () => { let Component; @@ -32,7 +32,6 @@ describe('IDE extra file row component', () => { afterEach(() => { vm.$destroy(); - resetStore(vm.$store); stagedFilesCount = 0; unstagedFilesCount = 0; diff --git a/spec/frontend/ide/components/file_templates/bar_spec.js b/spec/frontend/ide/components/file_templates/bar_spec.js index 21dbe18a223..5a33837fb14 100644 --- a/spec/frontend/ide/components/file_templates/bar_spec.js +++ b/spec/frontend/ide/components/file_templates/bar_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createStore } from '~/ide/stores'; import Bar from '~/ide/components/file_templates/bar.vue'; -import { resetStore, file } from '../../helpers'; +import { file } from '../../helpers'; describe('IDE file templates bar component', () => { let Component; @@ -26,7 +26,6 @@ describe('IDE file templates bar component', () => { afterEach(() => { vm.$destroy(); - resetStore(vm.$store); }); describe('template type dropdown', () => { diff --git a/spec/frontend/ide/components/ide_review_spec.js b/spec/frontend/ide/components/ide_review_spec.js index b56957e1f6d..c9ac2ac423d 100644 --- a/spec/frontend/ide/components/ide_review_spec.js +++ b/spec/frontend/ide/components/ide_review_spec.js @@ -3,7 +3,7 @@ import IdeReview from '~/ide/components/ide_review.vue'; import { createStore } from '~/ide/stores'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; import { trimText } from '../../helpers/text_helper'; -import { resetStore, file } from '../helpers'; +import { file } from '../helpers'; import { projectData } from '../mock_data'; describe('IDE review mode', () => { @@ -26,8 +26,6 @@ describe('IDE review mode', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders list of files', () => { diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js index 65cad2e7eb0..67257b40879 100644 --- a/spec/frontend/ide/components/ide_side_bar_spec.js +++ b/spec/frontend/ide/components/ide_side_bar_spec.js @@ -1,15 +1,17 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import ideSidebar from '~/ide/components/ide_side_bar.vue'; import { leftSidebarViews } from '~/ide/constants'; -import { resetStore } from '../helpers'; import { projectData } from '../mock_data'; describe('IdeSidebar', () => { let vm; + let store; beforeEach(() => { + store = createStore(); + const Component = Vue.extend(ideSidebar); store.state.currentProjectId = 'abcproject'; @@ -20,8 +22,6 @@ describe('IdeSidebar', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders a sidebar', () => { diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js index efc1d984dec..a7b07a9f0e2 100644 --- a/spec/frontend/ide/components/ide_spec.js +++ b/spec/frontend/ide/components/ide_spec.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createStore } from '~/ide/stores'; import ide from '~/ide/components/ide.vue'; -import { file, resetStore } from '../helpers'; +import { file } from '../helpers'; import { projectData } from '../mock_data'; import extendStore from '~/ide/stores/extend'; @@ -41,8 +41,6 @@ describe('ide component, empty repo', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders "New file" button in empty repo', done => { @@ -63,8 +61,6 @@ describe('ide component, non-empty repo', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('shows error message when set', done => { diff --git a/spec/frontend/ide/components/ide_tree_list_spec.js b/spec/frontend/ide/components/ide_tree_list_spec.js index 30f11db3153..4593ef6049b 100644 --- a/spec/frontend/ide/components/ide_tree_list_spec.js +++ b/spec/frontend/ide/components/ide_tree_list_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; import IdeTreeList from '~/ide/components/ide_tree_list.vue'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { resetStore, file } from '../helpers'; +import { file } from '../helpers'; import { projectData } from '../mock_data'; describe('IDE tree list', () => { @@ -10,6 +10,7 @@ describe('IDE tree list', () => { const normalBranchTree = [file('fileName')]; const emptyBranchTree = []; let vm; + let store; const bootstrapWithTree = (tree = normalBranchTree) => { store.state.currentProjectId = 'abcproject'; @@ -25,10 +26,12 @@ describe('IDE tree list', () => { }); }; + beforeEach(() => { + store = createStore(); + }); + afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); describe('normal branch', () => { diff --git a/spec/frontend/ide/components/ide_tree_spec.js b/spec/frontend/ide/components/ide_tree_spec.js index 01f007f09c3..899daa0bf57 100644 --- a/spec/frontend/ide/components/ide_tree_spec.js +++ b/spec/frontend/ide/components/ide_tree_spec.js @@ -1,14 +1,17 @@ import Vue from 'vue'; import IdeTree from '~/ide/components/ide_tree.vue'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { resetStore, file } from '../helpers'; +import { file } from '../helpers'; import { projectData } from '../mock_data'; describe('IdeRepoTree', () => { + let store; let vm; beforeEach(() => { + store = createStore(); + const IdeRepoTree = Vue.extend(IdeTree); store.state.currentProjectId = 'abcproject'; @@ -24,8 +27,6 @@ describe('IdeRepoTree', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders list of files', () => { diff --git a/spec/frontend/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js index 00781c16609..c6cebf36de3 100644 --- a/spec/frontend/ide/components/new_dropdown/index_spec.js +++ b/spec/frontend/ide/components/new_dropdown/index_spec.js @@ -1,13 +1,15 @@ import Vue from 'vue'; import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import newDropdown from '~/ide/components/new_dropdown/index.vue'; -import { resetStore } from '../../helpers'; describe('new dropdown component', () => { + let store; let vm; beforeEach(() => { + store = createStore(); + const component = Vue.extend(newDropdown); vm = createComponentWithStore(component, store, { @@ -30,8 +32,6 @@ describe('new dropdown component', () => { afterEach(() => { vm.$destroy(); - - resetStore(vm.$store); }); it('renders new file, upload and new directory links', () => { diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js index a9620d26313..8caa9c2b437 100644 --- a/spec/frontend/ide/helpers.js +++ b/spec/frontend/ide/helpers.js @@ -1,25 +1,5 @@ import * as pathUtils from 'path'; import { decorateData } from '~/ide/stores/utils'; -import state from '~/ide/stores/state'; -import commitState from '~/ide/stores/modules/commit/state'; -import mergeRequestsState from '~/ide/stores/modules/merge_requests/state'; -import pipelinesState from '~/ide/stores/modules/pipelines/state'; -import branchesState from '~/ide/stores/modules/branches/state'; -import fileTemplatesState from '~/ide/stores/modules/file_templates/state'; -import paneState from '~/ide/stores/modules/pane/state'; - -export const resetStore = store => { - const newState = { - ...state(), - commit: commitState(), - mergeRequests: mergeRequestsState(), - pipelines: pipelinesState(), - branches: branchesState(), - fileTemplates: fileTemplatesState(), - rightPane: paneState(), - }; - store.replaceState(newState); -}; export const file = (name = 'name', id = name, type = '', parent = null) => decorateData({ diff --git a/spec/frontend/ide/lib/decorations/controller_spec.js b/spec/frontend/ide/lib/decorations/controller_spec.js index 4556fc9d646..e9b7faaadfe 100644 --- a/spec/frontend/ide/lib/decorations/controller_spec.js +++ b/spec/frontend/ide/lib/decorations/controller_spec.js @@ -2,14 +2,17 @@ import Editor from '~/ide/lib/editor'; import DecorationsController from '~/ide/lib/decorations/controller'; import Model from '~/ide/lib/common/model'; import { file } from '../../helpers'; +import { createStore } from '~/ide/stores'; describe('Multi-file editor library decorations controller', () => { let editorInstance; let controller; let model; + let store; beforeEach(() => { - editorInstance = Editor.create(); + store = createStore(); + editorInstance = Editor.create(store); editorInstance.createInstance(document.createElement('div')); controller = new DecorationsController(editorInstance); diff --git a/spec/frontend/ide/lib/diff/controller_spec.js b/spec/frontend/ide/lib/diff/controller_spec.js index 0b33a4c6ad6..8ee6388a760 100644 --- a/spec/frontend/ide/lib/diff/controller_spec.js +++ b/spec/frontend/ide/lib/diff/controller_spec.js @@ -4,6 +4,7 @@ import ModelManager from '~/ide/lib/common/model_manager'; import DecorationsController from '~/ide/lib/decorations/controller'; import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller'; import { computeDiff } from '~/ide/lib/diff/diff'; +import { createStore } from '~/ide/stores'; import { file } from '../../helpers'; describe('Multi-file editor library dirty diff controller', () => { @@ -12,9 +13,12 @@ describe('Multi-file editor library dirty diff controller', () => { let modelManager; let decorationsController; let model; + let store; beforeEach(() => { - editorInstance = Editor.create(); + store = createStore(); + + editorInstance = Editor.create(store); editorInstance.createInstance(document.createElement('div')); modelManager = new ModelManager(); diff --git a/spec/frontend/ide/lib/editor_spec.js b/spec/frontend/ide/lib/editor_spec.js index 5f28309422d..529f80e6f6f 100644 --- a/spec/frontend/ide/lib/editor_spec.js +++ b/spec/frontend/ide/lib/editor_spec.js @@ -5,6 +5,7 @@ import { Selection, } from 'monaco-editor'; import Editor from '~/ide/lib/editor'; +import { createStore } from '~/ide/stores'; import { defaultEditorOptions } from '~/ide/lib/editor_options'; import { file } from '../helpers'; @@ -12,6 +13,7 @@ describe('Multi-file editor library', () => { let instance; let el; let holder; + let store; const setNodeOffsetWidth = val => { Object.defineProperty(instance.instance.getDomNode(), 'offsetWidth', { @@ -22,13 +24,14 @@ describe('Multi-file editor library', () => { }; beforeEach(() => { + store = createStore(); el = document.createElement('div'); holder = document.createElement('div'); el.appendChild(holder); document.body.appendChild(el); - instance = Editor.create(); + instance = Editor.create(store); }); afterEach(() => { @@ -44,7 +47,7 @@ describe('Multi-file editor library', () => { }); it('creates instance returns cached instance', () => { - expect(Editor.create()).toEqual(instance); + expect(Editor.create(store)).toEqual(instance); }); describe('createInstance', () => { diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js index e5c4f346459..9ca53a6b382 100644 --- a/spec/frontend/ide/stores/actions/merge_request_spec.js +++ b/spec/frontend/ide/stores/actions/merge_request_spec.js @@ -1,6 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import store from '~/ide/stores'; +import { createStore } from '~/ide/stores'; import createFlash from '~/flash'; import { getMergeRequestData, @@ -10,7 +10,6 @@ import { } from '~/ide/stores/actions/merge_request'; import service from '~/ide/services'; import { leftSidebarViews, PERMISSION_READ_MR } from '~/ide/constants'; -import { resetStore } from '../../helpers'; const TEST_PROJECT = 'abcproject'; const TEST_PROJECT_ID = 17; @@ -18,9 +17,12 @@ const TEST_PROJECT_ID = 17; jest.mock('~/flash'); describe('IDE store merge request actions', () => { + let store; let mock; beforeEach(() => { + store = createStore(); + mock = new MockAdapter(axios); store.state.projects[TEST_PROJECT] = { @@ -34,7 +36,6 @@ describe('IDE store merge request actions', () => { afterEach(() => { mock.restore(); - resetStore(store); }); describe('getMergeRequestsForBranch', () => { diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js index 5a1a615c703..7c841fbbbc5 100644 --- a/spec/frontend/monitoring/components/dashboard_header_spec.js +++ b/spec/frontend/monitoring/components/dashboard_header_spec.js @@ -1,16 +1,21 @@ import { shallowMount } from '@vue/test-utils'; import { createStore } from '~/monitoring/stores'; +import * as types from '~/monitoring/stores/mutation_types'; +import { GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui'; import DashboardHeader from '~/monitoring/components/dashboard_header.vue'; import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue'; import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue'; -import { setupAllDashboards } from '../store_utils'; +import { setupAllDashboards, setupStoreWithDashboard, setupStoreWithData } from '../store_utils'; import { + environmentData, dashboardGitResponse, selfMonitoringDashboardGitResponse, dashboardHeaderProps, } from '../mock_data'; import { redirectTo } from '~/lib/utils/url_utility'; +const mockProjectPath = 'https://path/to/project'; + jest.mock('~/lib/utils/url_utility', () => ({ redirectTo: jest.fn(), queryToObject: jest.fn(), @@ -21,6 +26,12 @@ describe('Dashboard header', () => { let store; let wrapper; + const findEnvsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' }); + const findEnvsDropdownItems = () => findEnvsDropdown().findAll(GlDropdownItem); + const findEnvsDropdownSearch = () => findEnvsDropdown().find(GlSearchBoxByType); + const findEnvsDropdownSearchMsg = () => wrapper.find({ ref: 'monitorEnvironmentsDropdownMsg' }); + const findEnvsDropdownLoadingIcon = () => findEnvsDropdown().find(GlLoadingIcon); + const findActionsMenu = () => wrapper.find('[data-testid="actions-menu"]'); const findCreateDashboardMenuItem = () => findActionsMenu().find('[data-testid="action-create-dashboard"]'); @@ -29,6 +40,10 @@ describe('Dashboard header', () => { const findDuplicateDashboardModal = () => wrapper.find(DuplicateDashboardModal); const findCreateDashboardModal = () => wrapper.find('[data-testid="create-dashboard-modal"]'); + const setSearchTerm = searchTerm => { + store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm); + }; + const createShallowWrapper = (props = {}, options = {}) => { wrapper = shallowMount(DashboardHeader, { propsData: { ...dashboardHeaderProps, ...props }, @@ -45,6 +60,113 @@ describe('Dashboard header', () => { wrapper.destroy(); }); + describe('environments dropdown', () => { + beforeEach(() => { + createShallowWrapper(); + }); + + it('shows the environments dropdown', () => { + expect(findEnvsDropdown().exists()).toBe(true); + }); + + it('renders a search input', () => { + expect(findEnvsDropdownSearch().exists()).toBe(true); + }); + + describe('when environments data is not loaded', () => { + beforeEach(() => { + setupStoreWithDashboard(store); + return wrapper.vm.$nextTick(); + }); + + it('there are no environments listed', () => { + expect(findEnvsDropdownItems().length).toBe(0); + }); + }); + + describe('when environments data is loaded', () => { + const currentDashboard = dashboardGitResponse[0].path; + + beforeEach(() => { + setupStoreWithData(store); + store.state.monitoringDashboard.projectPath = mockProjectPath; + store.state.monitoringDashboard.currentDashboard = currentDashboard; + + return wrapper.vm.$nextTick(); + }); + + it('renders dropdown items with the environment name', () => { + const path = `${mockProjectPath}/-/metrics/${encodeURIComponent(currentDashboard)}`; + + findEnvsDropdownItems().wrappers.forEach((itemWrapper, index) => { + const { name, id } = environmentData[index]; + const idParam = encodeURIComponent(id); + + expect(itemWrapper.text()).toBe(name); + expect(itemWrapper.attributes('href')).toBe(`${path}?environment=${idParam}`); + }); + }); + + // Note: This test is not working, .active does not show the active environment + // https://gitlab.com/gitlab-org/gitlab/-/issues/230615 + // eslint-disable-next-line jest/no-disabled-tests + it.skip('renders the environments dropdown with a single active element', () => { + const activeItem = findEnvsDropdownItems().wrappers.filter(itemWrapper => + itemWrapper.find('.active').exists(), + ); + + expect(activeItem.length).toBe(1); + }); + + it('filters rendered dropdown items', () => { + const searchTerm = 'production'; + const resultEnvs = environmentData.filter(({ name }) => name.indexOf(searchTerm) !== -1); + setSearchTerm(searchTerm); + + return wrapper.vm.$nextTick().then(() => { + expect(findEnvsDropdownItems().length).toBe(resultEnvs.length); + }); + }); + + it('does not filter dropdown items if search term is empty string', () => { + const searchTerm = ''; + setSearchTerm(searchTerm); + + return wrapper.vm.$nextTick(() => { + expect(findEnvsDropdownItems().length).toBe(environmentData.length); + }); + }); + + it("shows error message if search term doesn't match", () => { + const searchTerm = 'does-not-exist'; + setSearchTerm(searchTerm); + + return wrapper.vm.$nextTick(() => { + expect(findEnvsDropdownSearchMsg().isVisible()).toBe(true); + }); + }); + + it('shows loading element when environments fetch is still loading', () => { + store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`); + + return wrapper.vm + .$nextTick() + .then(() => { + expect(findEnvsDropdownLoadingIcon().exists()).toBe(true); + }) + .then(() => { + store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + }) + .then(() => { + expect(findEnvsDropdownLoadingIcon().exists()).toBe(false); + }); + }); + }); + }); + describe('when a dashboard has been duplicated in the duplicate dashboard modal', () => { beforeEach(() => { store.state.monitoringDashboard.projectPath = 'root/sandbox'; @@ -81,7 +203,7 @@ describe('Dashboard header', () => { }); it('is rendered if projectPath is set in store', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + store.state.monitoringDashboard.projectPath = mockProjectPath; return wrapper.vm.$nextTick().then(() => { expect(findActionsMenu().exists()).toBe(true); @@ -93,7 +215,7 @@ describe('Dashboard header', () => { }); it('contains a modal', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + store.state.monitoringDashboard.projectPath = mockProjectPath; return wrapper.vm.$nextTick().then(() => { expect(findActionsMenu().contains(CreateDashboardModal)).toBe(true); @@ -111,7 +233,7 @@ describe('Dashboard header', () => { 'when the selected dashboard can be duplicated', dashboardPath => { it('contains a "Create New" menu item and a "Duplicate Dashboard" menu item', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + store.state.monitoringDashboard.projectPath = mockProjectPath; setupAllDashboards(store, dashboardPath); return wrapper.vm.$nextTick().then(() => { @@ -131,7 +253,7 @@ describe('Dashboard header', () => { 'when the selected dashboard cannot be duplicated', dashboardPath => { it('contains a "Create New" menu item and no "Duplicate Dashboard" menu item', () => { - store.state.monitoringDashboard.projectPath = 'https://path/to/project'; + store.state.monitoringDashboard.projectPath = mockProjectPath; setupAllDashboards(store, dashboardPath); return wrapper.vm.$nextTick().then(() => { @@ -144,10 +266,8 @@ describe('Dashboard header', () => { }); describe('actions menu modals', () => { - const url = 'https://path/to/project'; - beforeEach(() => { - store.state.monitoringDashboard.projectPath = url; + store.state.monitoringDashboard.projectPath = mockProjectPath; setupAllDashboards(store); createShallowWrapper(); @@ -166,7 +286,7 @@ describe('Dashboard header', () => { }); it('"Create new dashboard" modal contains correct buttons', () => { - expect(findCreateDashboardModal().props('projectPath')).toBe(url); + expect(findCreateDashboardModal().props('projectPath')).toBe(mockProjectPath); }); it('"Duplicate Dashboard" opens up a modal', () => { diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 4b7f7a9ddb3..534c4c43d61 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -1,7 +1,7 @@ import { shallowMount, mount } from '@vue/test-utils'; import Tracking from '~/tracking'; import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys'; -import { GlModal, GlDropdownItem, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; +import { GlModal, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; import { objectToQuery } from '~/lib/utils/url_utility'; import VueDraggable from 'vuedraggable'; import MockAdapter from 'axios-mock-adapter'; @@ -29,7 +29,7 @@ import { setupStoreWithDataForPanelCount, setupStoreWithLinks, } from '../store_utils'; -import { environmentData, dashboardGitResponse, storeVariables } from '../mock_data'; +import { dashboardGitResponse, storeVariables } from '../mock_data'; import { metricsDashboardViewModel, metricsDashboardPanelCount, @@ -46,12 +46,6 @@ describe('Dashboard', () => { let mock; const findDashboardHeader = () => wrapper.find(DashboardHeader); - const findEnvironmentsDropdown = () => - findDashboardHeader().find({ ref: 'monitorEnvironmentsDropdown' }); - const findAllEnvironmentsDropdownItems = () => findEnvironmentsDropdown().findAll(GlDropdownItem); - const setSearchTerm = searchTerm => { - store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm); - }; const createShallowWrapper = (props = {}, options = {}) => { wrapper = shallowMount(Dashboard, { @@ -90,28 +84,6 @@ describe('Dashboard', () => { } }); - describe('no metrics are available yet', () => { - beforeEach(() => { - createShallowWrapper(); - }); - - it('shows the environment selector', () => { - expect(findEnvironmentsDropdown().exists()).toBe(true); - }); - }); - - describe('no data found', () => { - beforeEach(() => { - createShallowWrapper(); - - return wrapper.vm.$nextTick(); - }); - - it('shows the environment selector dropdown', () => { - expect(findEnvironmentsDropdown().exists()).toBe(true); - }); - }); - describe('request information to the server', () => { it('calls to set time range and fetch data', () => { createShallowWrapper({ hasMetrics: true }); @@ -149,17 +121,14 @@ describe('Dashboard', () => { }); it('fetches the metrics data with proper time window', () => { - jest.spyOn(store, 'dispatch'); - createMountedWrapper({ hasMetrics: true }); - store.commit( - `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, - environmentData, - ); - return wrapper.vm.$nextTick().then(() => { - expect(store.dispatch).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined); + expect(store.dispatch).toHaveBeenCalledWith( + 'monitoringDashboard/setTimeRange', + expect.objectContaining({ duration: { seconds: 28800 } }), + ); }); }); }); @@ -500,21 +469,6 @@ describe('Dashboard', () => { return wrapper.vm.$nextTick(); }); - it('renders the environments dropdown with a number of environments', () => { - expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length); - - findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => { - const anchorEl = itemWrapper.find('a'); - if (anchorEl.exists()) { - const href = anchorEl.attributes('href'); - const currentDashboard = encodeURIComponent(dashboardGitResponse[0].path); - const environmentId = encodeURIComponent(environmentData[index].id); - const url = `${TEST_HOST}/-/metrics/${currentDashboard}?environment=${environmentId}`; - expect(href).toBe(url); - } - }); - }); - it('it does not show loading icons in any group', () => { setupStoreWithData(store); @@ -524,16 +478,6 @@ describe('Dashboard', () => { }); }); }); - - // Note: This test is not working, .active does not show the active environment - // eslint-disable-next-line jest/no-disabled-tests - it.skip('renders the environments dropdown with a single active element', () => { - const activeItem = findAllEnvironmentsDropdownItems().wrappers.filter(itemWrapper => - itemWrapper.find('.active').exists(), - ); - - expect(activeItem.length).toBe(1); - }); }); describe('star dashboards', () => { @@ -615,16 +559,6 @@ describe('Dashboard', () => { }); }); - it('hides the environments dropdown list when there is no environments', () => { - createMountedWrapper({ hasMetrics: true }); - - setupStoreWithDashboard(store); - - return wrapper.vm.$nextTick().then(() => { - expect(findAllEnvironmentsDropdownItems()).toHaveLength(0); - }); - }); - it('renders the datetimepicker dropdown', () => { createMountedWrapper({ hasMetrics: true }); @@ -811,100 +745,6 @@ describe('Dashboard', () => { }); }); - describe('searchable environments dropdown', () => { - beforeEach(() => { - createMountedWrapper({ hasMetrics: true }, { attachToDocument: true }); - - setupStoreWithData(store); - - return wrapper.vm.$nextTick(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders a search input', () => { - expect( - wrapper - .find(DashboardHeader) - .find({ ref: 'monitorEnvironmentsDropdownSearch' }) - .exists(), - ).toBe(true); - }); - - it('renders dropdown items', () => { - findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => { - const anchorEl = itemWrapper.find('a'); - if (anchorEl.exists()) { - expect(anchorEl.text()).toBe(environmentData[index].name); - } - }); - }); - - it('filters rendered dropdown items', () => { - const searchTerm = 'production'; - const resultEnvs = environmentData.filter(({ name }) => name.indexOf(searchTerm) !== -1); - setSearchTerm(searchTerm); - - return wrapper.vm.$nextTick().then(() => { - expect(findAllEnvironmentsDropdownItems().length).toEqual(resultEnvs.length); - }); - }); - - it('does not filter dropdown items if search term is empty string', () => { - const searchTerm = ''; - setSearchTerm(searchTerm); - - return wrapper.vm.$nextTick(() => { - expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length); - }); - }); - - it("shows error message if search term doesn't match", () => { - const searchTerm = 'does-not-exist'; - setSearchTerm(searchTerm); - - return wrapper.vm.$nextTick(() => { - expect( - wrapper - .find(DashboardHeader) - .find({ ref: 'monitorEnvironmentsDropdownMsg' }) - .isVisible(), - ).toBe(true); - }); - }); - - it('shows loading element when environments fetch is still loading', () => { - store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`); - - return wrapper.vm - .$nextTick() - .then(() => { - expect( - wrapper - .find(DashboardHeader) - .find({ ref: 'monitorEnvironmentsDropdownLoading' }) - .exists(), - ).toBe(true); - }) - .then(() => { - store.commit( - `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, - environmentData, - ); - }) - .then(() => { - expect( - wrapper - .find(DashboardHeader) - .find({ ref: 'monitorEnvironmentsDropdownLoading' }) - .exists(), - ).toBe(false); - }); - }); - }); - describe('drag and drop function', () => { const findDraggables = () => wrapper.findAll(VueDraggable); const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled')); diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb deleted file mode 100644 index 6a25e8e2784..00000000000 --- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb +++ /dev/null @@ -1,263 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateUntrackedUploadsDependencies::UntrackedFile do - include MigrationsHelpers::TrackUntrackedUploadsHelpers - - let!(:appearances) { table(:appearances) } - let!(:namespaces) { table(:namespaces) } - let!(:projects) { table(:projects) } - let!(:routes) { table(:routes) } - let!(:uploads) { table(:uploads) } - - before(:all) do - ensure_temporary_tracking_table_exists - end - - describe '#upload_path' do - def assert_upload_path(file_path, expected_upload_path) - untracked_file = create_untracked_file(file_path) - - expect(untracked_file.upload_path).to eq(expected_upload_path) - end - - context 'for an appearance logo file path' do - it 'returns the file path relative to the CarrierWave root' do - assert_upload_path('/-/system/appearance/logo/1/some_logo.jpg', 'uploads/-/system/appearance/logo/1/some_logo.jpg') - end - end - - context 'for an appearance header_logo file path' do - it 'returns the file path relative to the CarrierWave root' do - assert_upload_path('/-/system/appearance/header_logo/1/some_logo.jpg', 'uploads/-/system/appearance/header_logo/1/some_logo.jpg') - end - end - - context 'for a pre-Markdown Note attachment file path' do - it 'returns the file path relative to the CarrierWave root' do - assert_upload_path('/-/system/note/attachment/1234/some_attachment.pdf', 'uploads/-/system/note/attachment/1234/some_attachment.pdf') - end - end - - context 'for a user avatar file path' do - it 'returns the file path relative to the CarrierWave root' do - assert_upload_path('/-/system/user/avatar/1234/avatar.jpg', 'uploads/-/system/user/avatar/1234/avatar.jpg') - end - end - - context 'for a group avatar file path' do - it 'returns the file path relative to the CarrierWave root' do - assert_upload_path('/-/system/group/avatar/1234/avatar.jpg', 'uploads/-/system/group/avatar/1234/avatar.jpg') - end - end - - context 'for a project avatar file path' do - it 'returns the file path relative to the CarrierWave root' do - assert_upload_path('/-/system/project/avatar/1234/avatar.jpg', 'uploads/-/system/project/avatar/1234/avatar.jpg') - end - end - - context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - it 'returns the file path relative to the project directory in uploads' do - project = create_project - random_hex = SecureRandom.hex - - assert_upload_path("/#{get_full_path(project)}/#{random_hex}/Some file.jpg", "#{random_hex}/Some file.jpg") - end - end - end - - describe '#uploader' do - def assert_uploader(file_path, expected_uploader) - untracked_file = create_untracked_file(file_path) - - expect(untracked_file.uploader).to eq(expected_uploader) - end - - context 'for an appearance logo file path' do - it 'returns AttachmentUploader as a string' do - assert_uploader('/-/system/appearance/logo/1/some_logo.jpg', 'AttachmentUploader') - end - end - - context 'for an appearance header_logo file path' do - it 'returns AttachmentUploader as a string' do - assert_uploader('/-/system/appearance/header_logo/1/some_logo.jpg', 'AttachmentUploader') - end - end - - context 'for a pre-Markdown Note attachment file path' do - it 'returns AttachmentUploader as a string' do - assert_uploader('/-/system/note/attachment/1234/some_attachment.pdf', 'AttachmentUploader') - end - end - - context 'for a user avatar file path' do - it 'returns AvatarUploader as a string' do - assert_uploader('/-/system/user/avatar/1234/avatar.jpg', 'AvatarUploader') - end - end - - context 'for a group avatar file path' do - it 'returns AvatarUploader as a string' do - assert_uploader('/-/system/group/avatar/1234/avatar.jpg', 'AvatarUploader') - end - end - - context 'for a project avatar file path' do - it 'returns AvatarUploader as a string' do - assert_uploader('/-/system/project/avatar/1234/avatar.jpg', 'AvatarUploader') - end - end - - context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - it 'returns FileUploader as a string' do - project = create_project - - assert_uploader("/#{get_full_path(project)}/#{SecureRandom.hex}/Some file.jpg", 'FileUploader') - end - end - end - - describe '#model_type' do - def assert_model_type(file_path, expected_model_type) - untracked_file = create_untracked_file(file_path) - - expect(untracked_file.model_type).to eq(expected_model_type) - end - - context 'for an appearance logo file path' do - it 'returns Appearance as a string' do - assert_model_type('/-/system/appearance/logo/1/some_logo.jpg', 'Appearance') - end - end - - context 'for an appearance header_logo file path' do - it 'returns Appearance as a string' do - assert_model_type('/-/system/appearance/header_logo/1/some_logo.jpg', 'Appearance') - end - end - - context 'for a pre-Markdown Note attachment file path' do - it 'returns Note as a string' do - assert_model_type('/-/system/note/attachment/1234/some_attachment.pdf', 'Note') - end - end - - context 'for a user avatar file path' do - it 'returns User as a string' do - assert_model_type('/-/system/user/avatar/1234/avatar.jpg', 'User') - end - end - - context 'for a group avatar file path' do - it 'returns Namespace as a string' do - assert_model_type('/-/system/group/avatar/1234/avatar.jpg', 'Namespace') - end - end - - context 'for a project avatar file path' do - it 'returns Project as a string' do - assert_model_type('/-/system/project/avatar/1234/avatar.jpg', 'Project') - end - end - - context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - it 'returns Project as a string' do - project = create_project - - assert_model_type("/#{get_full_path(project)}/#{SecureRandom.hex}/Some file.jpg", 'Project') - end - end - end - - describe '#model_id' do - def assert_model_id(file_path, expected_model_id) - untracked_file = create_untracked_file(file_path) - - expect(untracked_file.model_id).to eq(expected_model_id) - end - - context 'for an appearance logo file path' do - it 'returns the ID as a string' do - assert_model_id('/-/system/appearance/logo/1/some_logo.jpg', 1) - end - end - - context 'for an appearance header_logo file path' do - it 'returns the ID as a string' do - assert_model_id('/-/system/appearance/header_logo/1/some_logo.jpg', 1) - end - end - - context 'for a pre-Markdown Note attachment file path' do - it 'returns the ID as a string' do - assert_model_id('/-/system/note/attachment/1234/some_attachment.pdf', 1234) - end - end - - context 'for a user avatar file path' do - it 'returns the ID as a string' do - assert_model_id('/-/system/user/avatar/1234/avatar.jpg', 1234) - end - end - - context 'for a group avatar file path' do - it 'returns the ID as a string' do - assert_model_id('/-/system/group/avatar/1234/avatar.jpg', 1234) - end - end - - context 'for a project avatar file path' do - it 'returns the ID as a string' do - assert_model_id('/-/system/project/avatar/1234/avatar.jpg', 1234) - end - end - - context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - it 'returns the ID as a string' do - project = create_project - - assert_model_id("/#{get_full_path(project)}/#{SecureRandom.hex}/Some file.jpg", project.id) - end - end - end - - describe '#file_size' do - context 'for an appearance logo file path' do - let(:appearance) { create_or_update_appearance(logo: true) } - let(:untracked_file) { described_class.create!(path: get_uploads(appearance, 'Appearance').first.path) } - - it 'returns the file size' do - expect(untracked_file.file_size).to eq(1062) - end - end - - context 'for a project avatar file path' do - let(:project) { create_project(avatar: true) } - let(:untracked_file) { described_class.create!(path: get_uploads(project, 'Project').first.path) } - - it 'returns the file size' do - expect(untracked_file.file_size).to eq(1062) - end - end - - context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - let(:project) { create_project } - let(:untracked_file) { create_untracked_file("/#{get_full_path(project)}/#{get_uploads(project, 'Project').first.path}") } - - before do - add_markdown_attachment(project) - end - - it 'returns the file size' do - expect(untracked_file.file_size).to eq(1062) - end - end - end - - def create_untracked_file(path_relative_to_upload_dir) - described_class.create!(path: "#{Gitlab::BackgroundMigration::PrepareUntrackedUploads::RELATIVE_UPLOAD_DIR}#{path_relative_to_upload_dir}") - end -end diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb deleted file mode 100644 index 787cc54e79a..00000000000 --- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb +++ /dev/null @@ -1,254 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateUntrackedUploads do - include MigrationsHelpers::TrackUntrackedUploadsHelpers - - subject { described_class.new } - - let!(:appearances) { table(:appearances) } - let!(:namespaces) { table(:namespaces) } - let!(:notes) { table(:notes) } - let!(:projects) { table(:projects) } - let!(:routes) { table(:routes) } - let!(:untracked_files_for_uploads) { table(:untracked_files_for_uploads) } - let!(:uploads) { table(:uploads) } - let!(:users) { table(:users) } - - before do - ensure_temporary_tracking_table_exists - uploads.delete_all - end - - context 'with untracked files and tracked files in untracked_files_for_uploads' do - let!(:appearance) { create_or_update_appearance(logo: true, header_logo: true) } - let!(:user1) { create_user(avatar: true) } - let!(:user2) { create_user(avatar: true) } - let!(:project1) { create_project(avatar: true) } - let!(:project2) { create_project(avatar: true) } - - before do - add_markdown_attachment(project1) - add_markdown_attachment(project2) - - # File records created by PrepareUntrackedUploads - untracked_files_for_uploads.create!(path: get_uploads(appearance, 'Appearance').first.path) - untracked_files_for_uploads.create!(path: get_uploads(appearance, 'Appearance').last.path) - untracked_files_for_uploads.create!(path: get_uploads(user1, 'User').first.path) - untracked_files_for_uploads.create!(path: get_uploads(user2, 'User').first.path) - untracked_files_for_uploads.create!(path: get_uploads(project1, 'Project').first.path) - untracked_files_for_uploads.create!(path: get_uploads(project2, 'Project').first.path) - untracked_files_for_uploads.create!(path: "#{legacy_project_uploads_dir(project1).sub("#{MigrationsHelpers::TrackUntrackedUploadsHelpers::PUBLIC_DIR}/", '')}/#{get_uploads(project1, 'Project').last.path}") - untracked_files_for_uploads.create!(path: "#{legacy_project_uploads_dir(project2).sub("#{MigrationsHelpers::TrackUntrackedUploadsHelpers::PUBLIC_DIR}/", '')}/#{get_uploads(project2, 'Project').last.path}") - - # Untrack 4 files - get_uploads(user2, 'User').delete_all - get_uploads(project2, 'Project').delete_all # 2 files: avatar and a Markdown upload - get_uploads(appearance, 'Appearance').where("path like '%header_logo%'").delete_all - end - - it 'adds untracked files to the uploads table' do - expect do - subject.perform(1, untracked_files_for_uploads.reorder(:id).last.id) - end.to change { uploads.count }.from(4).to(8) - - expect(get_uploads(user2, 'User').count).to eq(1) - expect(get_uploads(project2, 'Project').count).to eq(2) - expect(get_uploads(appearance, 'Appearance').count).to eq(2) - end - - it 'deletes rows after processing them' do - expect(subject).to receive(:drop_temp_table_if_finished) # Don't drop the table so we can look at it - - expect do - subject.perform(1, untracked_files_for_uploads.last.id) - end.to change { untracked_files_for_uploads.count }.from(8).to(0) - end - - it 'does not create duplicate uploads of already tracked files' do - subject.perform(1, untracked_files_for_uploads.last.id) - - expect(get_uploads(user1, 'User').count).to eq(1) - expect(get_uploads(project1, 'Project').count).to eq(2) - expect(get_uploads(appearance, 'Appearance').count).to eq(2) - end - - it 'uses the start and end batch ids [only 1st half]' do - ids = untracked_files_for_uploads.all.order(:id).pluck(:id) - start_id = ids[0] - end_id = ids[3] - - expect do - subject.perform(start_id, end_id) - end.to change { uploads.count }.from(4).to(6) - - expect(get_uploads(user1, 'User').count).to eq(1) - expect(get_uploads(user2, 'User').count).to eq(1) - expect(get_uploads(appearance, 'Appearance').count).to eq(2) - expect(get_uploads(project1, 'Project').count).to eq(2) - expect(get_uploads(project2, 'Project').count).to eq(0) - - # Only 4 have been either confirmed or added to uploads - expect(untracked_files_for_uploads.count).to eq(4) - end - - it 'uses the start and end batch ids [only 2nd half]' do - ids = untracked_files_for_uploads.all.order(:id).pluck(:id) - start_id = ids[4] - end_id = ids[7] - - expect do - subject.perform(start_id, end_id) - end.to change { uploads.count }.from(4).to(6) - - expect(get_uploads(user1, 'User').count).to eq(1) - expect(get_uploads(user2, 'User').count).to eq(0) - expect(get_uploads(appearance, 'Appearance').count).to eq(1) - expect(get_uploads(project1, 'Project').count).to eq(2) - expect(get_uploads(project2, 'Project').count).to eq(2) - - # Only 4 have been either confirmed or added to uploads - expect(untracked_files_for_uploads.count).to eq(4) - end - - it 'does not drop the temporary tracking table after processing the batch, if there are still untracked rows' do - subject.perform(1, untracked_files_for_uploads.last.id - 1) - - expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_truthy - end - - it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do - expect(subject).to receive(:drop_temp_table_if_finished) - - subject.perform(1, untracked_files_for_uploads.last.id) - end - - it 'does not block a whole batch because of one bad path' do - untracked_files_for_uploads.create!(path: "#{Gitlab::BackgroundMigration::PrepareUntrackedUploads::RELATIVE_UPLOAD_DIR}/#{get_full_path(project2)}/._7d37bf4c747916390e596744117d5d1a") - expect(untracked_files_for_uploads.count).to eq(9) - expect(uploads.count).to eq(4) - - subject.perform(1, untracked_files_for_uploads.last.id) - - expect(untracked_files_for_uploads.count).to eq(1) - expect(uploads.count).to eq(8) - end - - it 'an unparseable path is shown in error output' do - bad_path = "#{Gitlab::BackgroundMigration::PrepareUntrackedUploads::RELATIVE_UPLOAD_DIR}/#{get_full_path(project2)}/._7d37bf4c747916390e596744117d5d1a" - untracked_files_for_uploads.create!(path: bad_path) - - expect(Rails.logger).to receive(:error).with(/Error parsing path "#{bad_path}":/) - - subject.perform(1, untracked_files_for_uploads.last.id) - end - end - - context 'with no untracked files' do - it 'does not add to the uploads table (and does not raise error)' do - expect do - subject.perform(1, 1000) - end.not_to change { uploads.count }.from(0) - end - end - - describe 'upload outcomes for each path pattern' do - shared_examples_for 'non_markdown_file' do - let!(:expected_upload_attrs) { model_uploads.first.attributes.slice('path', 'uploader', 'size', 'checksum') } - let!(:untracked_file) { untracked_files_for_uploads.create!(path: expected_upload_attrs['path']) } - - before do - model_uploads.delete_all - end - - it 'creates an Upload record' do - expect do - subject.perform(1, untracked_files_for_uploads.last.id) - end.to change { model_uploads.count }.from(0).to(1) - - expect(model_uploads.first.attributes).to include(expected_upload_attrs) - end - end - - context 'for an appearance logo file path' do - let(:model) { create_or_update_appearance(logo: true) } - let(:model_uploads) { get_uploads(model, 'Appearance') } - - it_behaves_like 'non_markdown_file' - end - - context 'for an appearance header_logo file path' do - let(:model) { create_or_update_appearance(header_logo: true) } - let(:model_uploads) { get_uploads(model, 'Appearance') } - - it_behaves_like 'non_markdown_file' - end - - context 'for a pre-Markdown Note attachment file path' do - let(:model) { create_note(attachment: true) } - let!(:expected_upload_attrs) { get_uploads(model, 'Note').first.attributes.slice('path', 'uploader', 'size', 'checksum') } - let!(:untracked_file) { untracked_files_for_uploads.create!(path: expected_upload_attrs['path']) } - - before do - get_uploads(model, 'Note').delete_all - end - - # Can't use the shared example because Note doesn't have an `uploads` association - it 'creates an Upload record' do - expect do - subject.perform(1, untracked_files_for_uploads.last.id) - end.to change { get_uploads(model, 'Note').count }.from(0).to(1) - - expect(get_uploads(model, 'Note').first.attributes).to include(expected_upload_attrs) - end - end - - context 'for a user avatar file path' do - let(:model) { create_user(avatar: true) } - let(:model_uploads) { get_uploads(model, 'User') } - - it_behaves_like 'non_markdown_file' - end - - context 'for a group avatar file path' do - let(:model) { create_group(avatar: true) } - let(:model_uploads) { get_uploads(model, 'Namespace') } - - it_behaves_like 'non_markdown_file' - end - - context 'for a project avatar file path' do - let(:model) { create_project(avatar: true) } - let(:model_uploads) { get_uploads(model, 'Project') } - - it_behaves_like 'non_markdown_file' - end - - context 'for a project Markdown attachment (notes, issues, MR descriptions) file path' do - let(:model) { create_project } - - before do - # Upload the file - add_markdown_attachment(model) - - # Create the untracked_files_for_uploads record - untracked_files_for_uploads.create!(path: "#{Gitlab::BackgroundMigration::PrepareUntrackedUploads::RELATIVE_UPLOAD_DIR}/#{get_full_path(model)}/#{get_uploads(model, 'Project').first.path}") - - # Save the expected upload attributes - @expected_upload_attrs = get_uploads(model, 'Project').first.attributes.slice('path', 'uploader', 'size', 'checksum') - - # Untrack the file - get_uploads(model, 'Project').delete_all - end - - it 'creates an Upload record' do - expect do - subject.perform(1, untracked_files_for_uploads.last.id) - end.to change { get_uploads(model, 'Project').count }.from(0).to(1) - - expect(get_uploads(model, 'Project').first.attributes).to include(@expected_upload_attrs) - end - end - end -end diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb deleted file mode 100644 index 9b01407dc8b..00000000000 --- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work. -RSpec.describe Gitlab::BackgroundMigration::PrepareUntrackedUploads do - include MigrationsHelpers::TrackUntrackedUploadsHelpers - - let!(:untracked_files_for_uploads) { table(:untracked_files_for_uploads) } - let!(:appearances) { table(:appearances) } - let!(:namespaces) { table(:namespaces) } - let!(:projects) { table(:projects) } - let!(:routes) { table(:routes) } - let!(:uploads) { table(:uploads) } - let!(:users) { table(:users) } - - around do |example| - # Especially important so the follow-up migration does not get run - Sidekiq::Testing.fake! do - example.run - end - end - - shared_examples 'prepares the untracked_files_for_uploads table' do - context 'when files were uploaded before and after hashed storage was enabled' do - let!(:appearance) { create_or_update_appearance(logo: true, header_logo: true) } - let!(:user) { create_user(avatar: true) } - let!(:project1) { create_project(avatar: true) } - let(:project2) { create_project } # instantiate after enabling hashed_storage - - before do - # Markdown upload before enabling hashed_storage - add_markdown_attachment(project1) - - # Markdown upload after enabling hashed_storage - add_markdown_attachment(project2, hashed_storage: true) - end - - it 'has a path field long enough for really long paths' do - described_class.new.perform - - component = 'a' * 255 - - long_path = [ - 'uploads', - component, # project.full_path - component # filename - ].flatten.join('/') - - record = untracked_files_for_uploads.create!(path: long_path) - expect(record.reload.path.size).to eq(519) - end - - it 'adds unhashed files to the untracked_files_for_uploads table' do - described_class.new.perform - - expect(untracked_files_for_uploads.count).to eq(5) - end - - it 'adds files with paths relative to CarrierWave.root' do - described_class.new.perform - untracked_files_for_uploads.all.each do |file| - expect(file.path.start_with?('uploads/')).to be_truthy - end - end - - it 'does not add hashed files to the untracked_files_for_uploads table' do - described_class.new.perform - - hashed_file_path = get_uploads(project2, 'Project').find_by(uploader: 'FileUploader').path - expect(untracked_files_for_uploads.where("path like '%#{hashed_file_path}%'").exists?).to be_falsey - end - - it 'correctly schedules the follow-up background migration jobs' do - described_class.new.perform - - ids = described_class::UntrackedFile.all.order(:id).pluck(:id) - expect(described_class::FOLLOW_UP_MIGRATION).to be_scheduled_migration(ids.first, ids.last) - expect(BackgroundMigrationWorker.jobs.size).to eq(1) - end - - # E.g. from a previous failed run of this background migration - context 'when there is existing data in untracked_files_for_uploads' do - before do - described_class.new.perform - end - - it 'does not error or produce duplicates of existing data' do - expect do - described_class.new.perform - end.not_to change { untracked_files_for_uploads.count }.from(5) - end - end - - # E.g. The installation is in use at the time of migration, and someone has - # just uploaded a file - context 'when there are files in /uploads/tmp' do - let(:tmp_file) { Rails.root.join(described_class::ABSOLUTE_UPLOAD_DIR, 'tmp', 'some_file.jpg') } - - before do - FileUtils.mkdir(File.dirname(tmp_file)) - FileUtils.touch(tmp_file) - end - - after do - FileUtils.rm(tmp_file) - end - - it 'does not add files from /uploads/tmp' do - described_class.new.perform - - expect(untracked_files_for_uploads.count).to eq(5) - end - end - - context 'when the last batch size exactly matches the max batch size' do - it 'does not raise error' do - stub_const("#{described_class}::FIND_BATCH_SIZE", 5) - - expect do - described_class.new.perform - end.not_to raise_error - - expect(untracked_files_for_uploads.count).to eq(5) - end - end - end - end - - # If running on Postgres 9.2 (like on CI), this whole context is skipped - # since we're unable to use ON CONFLICT DO NOTHING or IGNORE. - context "test bulk insert with ON CONFLICT DO NOTHING or IGNORE", if: described_class.new.send(:can_bulk_insert_and_ignore_duplicates?) do - it_behaves_like 'prepares the untracked_files_for_uploads table' - end - - # If running on Postgres 9.2 (like on CI), the stubbed method has no effect. - # - # If running on Postgres 9.5+ or MySQL, then this context effectively tests - # the bulk insert functionality without ON CONFLICT DO NOTHING or IGNORE. - context 'test bulk insert without ON CONFLICT DO NOTHING or IGNORE' do - before do - allow_any_instance_of(described_class).to receive(:postgresql_pre_9_5?).and_return(true) - end - - it_behaves_like 'prepares the untracked_files_for_uploads table' - end - - # Very new or lightly-used installations that are running this migration - # may not have an upload directory because they have no uploads. - context 'when no files were ever uploaded' do - it 'deletes the `untracked_files_for_uploads` table (and does not raise error)' do - background_migration = described_class.new - - expect(background_migration).to receive(:drop_temp_table) - - background_migration.perform - end - end -end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index f733c066127..a340e69df65 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -574,10 +574,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do subject { described_class.components_usage_data } it 'gathers basic components usage data' do - stub_runtime(:puma) stub_application_setting(container_registry_vendor: 'gitlab', container_registry_version: 'x.y.z') - expect(subject[:app_server][:type]).to eq('puma') expect(subject[:gitlab_pages][:enabled]).to eq(Gitlab.config.pages.enabled) expect(subject[:gitlab_pages][:version]).to eq(Gitlab::Pages::VERSION) expect(subject[:git][:version]).to eq(Gitlab::Git.version) @@ -591,32 +589,6 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do expect(subject[:container_registry][:vendor]).to eq('gitlab') expect(subject[:container_registry][:version]).to eq('x.y.z') end - - def stub_runtime(runtime) - allow(Gitlab::Runtime).to receive(:identify).and_return(runtime) - end - end - - describe '.app_server_type' do - subject { described_class.app_server_type } - - it 'successfully identifies runtime and returns the identifier' do - expect(Gitlab::Runtime).to receive(:identify).and_return(:runtime_identifier) - - is_expected.to eq('runtime_identifier') - end - - context 'when runtime is not identified' do - let(:exception) { Gitlab::Runtime::IdentificationError.new('exception message from runtime identify') } - - it 'logs the exception and returns unknown app server type' do - expect(Gitlab::Runtime).to receive(:identify).and_raise(exception) - - expect(Gitlab::AppLogger).to receive(:error).with(exception.message) - expect(Gitlab::ErrorTracking).to receive(:track_exception).with(exception) - expect(subject).to eq('unknown_app_server_type') - end - end end describe '.object_store_config' do diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb index 34d3c54d700..78a2688ac5e 100644 --- a/spec/requests/api/suggestions_spec.rb +++ b/spec/requests/api/suggestions_spec.rb @@ -74,7 +74,7 @@ RSpec.describe API::Suggestions do put api(url, user) expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response).to eq({ 'message' => 'A suggestion is not applicable.' }) + expect(json_response).to eq({ 'message' => "Can't apply as this line was changed in a more recent version." }) end end @@ -133,7 +133,7 @@ RSpec.describe API::Suggestions do params: { ids: [suggestion.id, unappliable_suggestion.id] } expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response).to eq({ 'message' => 'A suggestion is not applicable.' }) + expect(json_response).to eq({ 'message' => "Can't apply as this line was changed in a more recent version." }) end end diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb index aa9caf35987..d8ade0fbbda 100644 --- a/spec/services/suggestions/apply_service_spec.rb +++ b/spec/services/suggestions/apply_service_spec.rb @@ -609,40 +609,16 @@ RSpec.describe Suggestions::ApplyService do end end - context 'suggestion is eligible to be outdated' do - it 'returns error message' do - expect(suggestion).to receive(:outdated?) { true } - - result = apply_service.new(user, suggestion).execute - - expect(result).to eq(message: 'A suggestion is not applicable.', - status: :error) - end - end - - context 'note is outdated' do - before do - allow(diff_note).to receive(:active?) { false } - end + context 'suggestion is not appliable' do + let(:inapplicable_reason) { "Can't apply this suggestion." } it 'returns error message' do - result = apply_service.new(user, suggestion).execute - - expect(result).to eq(message: 'A suggestion is not applicable.', - status: :error) - end - end + expect(suggestion).to receive(:appliable?).and_return(false) + expect(suggestion).to receive(:inapplicable_reason).and_return(inapplicable_reason) - context 'suggestion was already applied' do - before do - suggestion.update!(applied: true, commit_id: 'sha') - end - - it 'returns error message' do result = apply_service.new(user, suggestion).execute - expect(result).to eq(message: 'A suggestion is not applicable.', - status: :error) + expect(result).to eq(message: inapplicable_reason, status: :error) end end diff --git a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb deleted file mode 100644 index 656be3b6d4d..00000000000 --- a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -module MigrationsHelpers - module TrackUntrackedUploadsHelpers - PUBLIC_DIR = File.join(Rails.root, 'tmp', 'tests', 'public') - UPLOADS_DIR = File.join(PUBLIC_DIR, 'uploads') - SYSTEM_DIR = File.join(UPLOADS_DIR, '-', 'system') - UPLOAD_FILENAME = 'image.png'.freeze - FIXTURE_FILE_PATH = File.join(Rails.root, 'spec', 'fixtures', 'dk.png') - FIXTURE_CHECKSUM = 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75'.freeze - - def create_or_update_appearance(logo: false, header_logo: false) - appearance = appearances.first_or_create(title: 'foo', description: 'bar', logo: (UPLOAD_FILENAME if logo), header_logo: (UPLOAD_FILENAME if header_logo)) - - add_upload(appearance, 'Appearance', 'logo', 'AttachmentUploader') if logo - add_upload(appearance, 'Appearance', 'header_logo', 'AttachmentUploader') if header_logo - - appearance - end - - def create_group(avatar: false) - index = unique_index(:group) - group = namespaces.create(name: "group#{index}", path: "group#{index}", avatar: (UPLOAD_FILENAME if avatar)) - - add_upload(group, 'Group', 'avatar', 'AvatarUploader') if avatar - - group - end - - def create_note(attachment: false) - note = notes.create(attachment: (UPLOAD_FILENAME if attachment)) - - add_upload(note, 'Note', 'attachment', 'AttachmentUploader') if attachment - - note - end - - def create_project(avatar: false) - group = create_group - project = projects.create(namespace_id: group.id, path: "project#{unique_index(:project)}", avatar: (UPLOAD_FILENAME if avatar)) - routes.create(path: "#{group.path}/#{project.path}", source_id: project.id, source_type: 'Project') # so Project.find_by_full_path works - - add_upload(project, 'Project', 'avatar', 'AvatarUploader') if avatar - - project - end - - def create_user(avatar: false) - user = users.create(email: "foo#{unique_index(:user)}@bar.com", avatar: (UPLOAD_FILENAME if avatar), projects_limit: 100) - - add_upload(user, 'User', 'avatar', 'AvatarUploader') if avatar - - user - end - - def unique_index(name = :unnamed) - @unique_index ||= {} - @unique_index[name] ||= 0 - @unique_index[name] += 1 - end - - def add_upload(model, model_type, attachment_type, uploader) - file_path = upload_file_path(model, model_type, attachment_type) - path_relative_to_public = file_path.sub("#{PUBLIC_DIR}/", '') - create_file(file_path) - - uploads.create!( - size: 1062, - path: path_relative_to_public, - model_id: model.id, - model_type: model_type == 'Group' ? 'Namespace' : model_type, - uploader: uploader, - checksum: FIXTURE_CHECKSUM - ) - end - - def add_markdown_attachment(project, hashed_storage: false) - project_dir = hashed_storage ? hashed_project_uploads_dir(project) : legacy_project_uploads_dir(project) - attachment_dir = File.join(project_dir, SecureRandom.hex) - attachment_file_path = File.join(attachment_dir, UPLOAD_FILENAME) - project_attachment_path_relative_to_project = attachment_file_path.sub("#{project_dir}/", '') - create_file(attachment_file_path) - - uploads.create!( - size: 1062, - path: project_attachment_path_relative_to_project, - model_id: project.id, - model_type: 'Project', - uploader: 'FileUploader', - checksum: FIXTURE_CHECKSUM - ) - end - - def legacy_project_uploads_dir(project) - namespace = namespaces.find_by(id: project.namespace_id) - File.join(UPLOADS_DIR, namespace.path, project.path) - end - - def hashed_project_uploads_dir(project) - File.join(UPLOADS_DIR, '@hashed', 'aa', 'aaaaaaaaaaaa') - end - - def upload_file_path(model, model_type, attachment_type) - dir = File.join(upload_dir(model_type.downcase, attachment_type.to_s), model.id.to_s) - File.join(dir, UPLOAD_FILENAME) - end - - def upload_dir(model_type, attachment_type) - File.join(SYSTEM_DIR, model_type, attachment_type) - end - - def create_file(path) - File.delete(path) if File.exist?(path) - FileUtils.mkdir_p(File.dirname(path)) - FileUtils.cp(FIXTURE_FILE_PATH, path) - end - - def get_uploads(model, model_type) - uploads.where(model_type: model_type, model_id: model.id) - end - - def get_full_path(project) - routes.find_by(source_id: project.id, source_type: 'Project').path - end - - def ensure_temporary_tracking_table_exists - Gitlab::BackgroundMigration::PrepareUntrackedUploads.new.send(:ensure_temporary_tracking_table_exists) - end - end -end |