Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-08 21:10:23 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-08 21:10:23 +0300
commit8a03b5424b73679d852fbf43781d9422c83869f7 (patch)
treea24a9c56fce9530492d60a640572922dd65d1072 /spec
parent0ebbf19f2d2b87e1f2aca1c59efde1aa6a766cf6 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/docs_screenshots/wiki_docs.rb47
-rw-r--r--spec/frontend/content_editor/components/top_toolbar_spec.js1
-rw-r--r--spec/frontend/content_editor/extensions/code_block_highlight_spec.js37
-rw-r--r--spec/frontend/pages/shared/wikis/components/wiki_form_spec.js56
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/user_callout_dismisser_mock_data.js30
-rw-r--r--spec/frontend/vue_shared/components/user_callout_dismisser_spec.js306
-rw-r--r--spec/lib/gitlab/import_export/shared_spec.rb22
-rw-r--r--spec/models/import_export_upload_spec.rb19
-rw-r--r--spec/models/pages/lookup_path_spec.rb9
-rw-r--r--spec/services/import_export_clean_up_service_spec.rb77
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb1
12 files changed, 567 insertions, 40 deletions
diff --git a/spec/docs_screenshots/wiki_docs.rb b/spec/docs_screenshots/wiki_docs.rb
new file mode 100644
index 00000000000..ce30b07182c
--- /dev/null
+++ b/spec/docs_screenshots/wiki_docs.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Wiki', :js do
+ include DocsScreenshotHelpers
+ include WikiHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace, creator: user) }
+ let(:wiki) { create(:project_wiki, user: user, project: project) }
+
+ before do
+ page.driver.browser.manage.window.resize_to(1366, 1024)
+
+ sign_in(user)
+ visit wiki_path(wiki)
+
+ click_link "Create your first page"
+ end
+
+ context 'switching to content editor' do
+ it 'user/project/wiki/img/use_new_editor_button' do
+ screenshot_area = find('.js-quick-submit')
+ scroll_to screenshot_area
+ expect(screenshot_area).to have_content 'Use new editor'
+ set_crop_data(screenshot_area, 10)
+ end
+ end
+
+ context 'content editor' do
+ it 'user/project/wiki/img/content_editor' do
+ content_editor_testid = '[data-testid="content-editor"]'
+
+ click_button 'Use new editor'
+
+ expect(page).to have_css(content_editor_testid)
+
+ screenshot_area = find(content_editor_testid)
+ scroll_to screenshot_area
+
+ find("#{content_editor_testid} [contenteditable]").send_keys '## Using the Content Editor'
+
+ set_crop_data(screenshot_area, 50)
+ end
+ end
+end
diff --git a/spec/frontend/content_editor/components/top_toolbar_spec.js b/spec/frontend/content_editor/components/top_toolbar_spec.js
index a4d5c1ac606..0892bc1a4da 100644
--- a/spec/frontend/content_editor/components/top_toolbar_spec.js
+++ b/spec/frontend/content_editor/components/top_toolbar_spec.js
@@ -46,6 +46,7 @@ describe('content_editor/components/top_toolbar', () => {
${'blockquote'} | ${{ contentType: 'blockquote', iconName: 'quote', label: 'Insert a quote', editorCommand: 'toggleBlockquote' }}
${'bullet-list'} | ${{ contentType: 'bulletList', iconName: 'list-bulleted', label: 'Add a bullet list', editorCommand: 'toggleBulletList' }}
${'ordered-list'} | ${{ contentType: 'orderedList', iconName: 'list-numbered', label: 'Add a numbered list', editorCommand: 'toggleOrderedList' }}
+ ${'code-block'} | ${{ contentType: 'codeBlock', iconName: 'doc-code', label: 'Insert a code block', editorCommand: 'toggleCodeBlock' }}
${'text-styles'} | ${{}}
`('given a $testId toolbar control', ({ testId, controlProps }) => {
beforeEach(() => {
diff --git a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js
new file mode 100644
index 00000000000..cc695ffe241
--- /dev/null
+++ b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js
@@ -0,0 +1,37 @@
+import { tiptapExtension as CodeBlockHighlight } from '~/content_editor/extensions/code_block_highlight';
+import { loadMarkdownApiResult } from '../markdown_processing_examples';
+import { createTestEditor } from '../test_utils';
+
+describe('content_editor/extensions/code_block_highlight', () => {
+ let codeBlockHtmlFixture;
+ let parsedCodeBlockHtmlFixture;
+ let tiptapEditor;
+
+ const parseHTML = (html) => new DOMParser().parseFromString(html, 'text/html');
+ const preElement = () => parsedCodeBlockHtmlFixture.querySelector('pre');
+
+ beforeEach(() => {
+ const { html } = loadMarkdownApiResult('code_block');
+
+ tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight] });
+ codeBlockHtmlFixture = html;
+ parsedCodeBlockHtmlFixture = parseHTML(codeBlockHtmlFixture);
+
+ tiptapEditor.commands.setContent(codeBlockHtmlFixture);
+ });
+
+ it('extracts language and params attributes from Markdown API output', () => {
+ const language = preElement().getAttribute('lang');
+
+ expect(tiptapEditor.getJSON().content[0].attrs).toMatchObject({
+ language,
+ params: language,
+ });
+ });
+
+ it('adds code, highlight, and js-syntax-highlight to code block element', () => {
+ const editorHtmlOutput = parseHTML(tiptapEditor.getHTML()).querySelector('pre');
+
+ expect(editorHtmlOutput.classList.toString()).toContain('code highlight js-syntax-highlight');
+ });
+});
diff --git a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
index 1cac8ef8ee2..1d210edb6ac 100644
--- a/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
+++ b/spec/frontend/pages/shared/wikis/components/wiki_form_spec.js
@@ -2,15 +2,23 @@ import { GlLoadingIcon, GlModal } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
+import { mockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ContentEditor from '~/content_editor/components/content_editor.vue';
import WikiForm from '~/pages/shared/wikis/components/wiki_form.vue';
+import {
+ WIKI_CONTENT_EDITOR_TRACKING_LABEL,
+ CONTENT_EDITOR_LOADED_ACTION,
+ SAVED_USING_CONTENT_EDITOR_ACTION,
+} from '~/pages/shared/wikis/constants';
+
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
describe('WikiForm', () => {
let wrapper;
let mock;
+ let trackingSpy;
const findForm = () => wrapper.find('form');
const findTitle = () => wrapper.find('#wiki_title');
@@ -60,10 +68,7 @@ describe('WikiForm', () => {
path: '/project/path/-/wikis/home',
};
- function createWrapper(
- persisted = false,
- { pageInfo, glFeatures } = { glFeatures: { wikiContentEditor: false } },
- ) {
+ function createWrapper(persisted = false, { pageInfo } = {}) {
wrapper = extendedWrapper(
mount(
WikiForm,
@@ -79,7 +84,6 @@ describe('WikiForm', () => {
...(persisted ? pageInfoPersisted : pageInfoNew),
...pageInfo,
},
- glFeatures,
},
},
{ attachToDocument: true },
@@ -88,6 +92,7 @@ describe('WikiForm', () => {
}
beforeEach(() => {
+ trackingSpy = mockTracking(undefined, null, jest.spyOn);
mock = new MockAdapter(axios);
});
@@ -193,13 +198,21 @@ describe('WikiForm', () => {
expect(e.preventDefault).toHaveBeenCalledTimes(1);
});
- it('when form submitted, unsets before unload warning', async () => {
- triggerFormSubmit();
+ describe('form submit', () => {
+ beforeEach(async () => {
+ triggerFormSubmit();
- await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
+ });
- const e = dispatchBeforeUnload();
- expect(e.preventDefault).not.toHaveBeenCalled();
+ it('when form submitted, unsets before unload warning', async () => {
+ const e = dispatchBeforeUnload();
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does not trigger tracking event', async () => {
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
});
});
@@ -251,9 +264,9 @@ describe('WikiForm', () => {
);
});
- describe('when feature flag wikiContentEditor is enabled', () => {
+ describe('wiki content editor', () => {
beforeEach(() => {
- createWrapper(true, { glFeatures: { wikiContentEditor: true } });
+ createWrapper(true);
});
it.each`
@@ -368,6 +381,15 @@ describe('WikiForm', () => {
expect(wrapper.findComponent(ContentEditor).exists()).toBe(true);
});
+ it('sends tracking event when editor loads', async () => {
+ // wait for content editor to load
+ await waitForPromises();
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, CONTENT_EDITOR_LOADED_ACTION, {
+ label: WIKI_CONTENT_EDITOR_TRACKING_LABEL,
+ });
+ });
+
it('disables the format dropdown', () => {
expect(findFormat().element.getAttribute('disabled')).toBeDefined();
});
@@ -400,6 +422,16 @@ describe('WikiForm', () => {
});
});
+ it('triggers tracking event on form submit', async () => {
+ triggerFormSubmit();
+
+ await wrapper.vm.$nextTick();
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, SAVED_USING_CONTENT_EDITOR_ACTION, {
+ label: WIKI_CONTENT_EDITOR_TRACKING_LABEL,
+ });
+ });
+
it('updates content from content editor on form submit', async () => {
// old value
expect(findContent().element.value).toBe('My page content');
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 4914a9a1ced..bb7e27b5ec2 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
+import getUserCallouts from '~/graphql_shared/queries/get_user_callouts.query.graphql';
import {
IID_FAILURE,
LAYER_VIEW,
@@ -17,7 +18,6 @@ import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.
import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue';
import * as parsingUtils from '~/pipelines/components/parsing_utils';
-import getUserCallouts from '~/pipelines/graphql/queries/get_user_callouts.query.graphql';
import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data';
const defaultProvide = {
diff --git a/spec/frontend/vue_shared/components/user_callout_dismisser_mock_data.js b/spec/frontend/vue_shared/components/user_callout_dismisser_mock_data.js
new file mode 100644
index 00000000000..7ca8c619ffc
--- /dev/null
+++ b/spec/frontend/vue_shared/components/user_callout_dismisser_mock_data.js
@@ -0,0 +1,30 @@
+export const userCalloutsResponse = (callouts = []) => ({
+ data: {
+ currentUser: {
+ id: 'gid://gitlab/User/46',
+ __typename: 'UserCore',
+ callouts: {
+ __typename: 'UserCalloutConnection',
+ nodes: callouts.map((callout) => ({
+ __typename: 'UserCallout',
+ featureName: callout.toUpperCase(),
+ dismissedAt: '2021-02-12T11:10:01Z',
+ })),
+ },
+ },
+ },
+});
+
+export const anonUserCalloutsResponse = () => ({ data: { currentUser: null } });
+
+export const userCalloutMutationResponse = (variables, errors = []) => ({
+ data: {
+ userCalloutCreate: {
+ errors,
+ userCallout: {
+ featureName: variables.input.featureName.toUpperCase(),
+ dismissedAt: '2021-02-12T11:10:01Z',
+ },
+ },
+ },
+});
diff --git a/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js b/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
new file mode 100644
index 00000000000..70dec42ab32
--- /dev/null
+++ b/spec/frontend/vue_shared/components/user_callout_dismisser_spec.js
@@ -0,0 +1,306 @@
+import { mount } from '@vue/test-utils';
+import { merge } from 'lodash';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import dismissUserCalloutMutation from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
+import getUserCalloutsQuery from '~/graphql_shared/queries/get_user_callouts.query.graphql';
+import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
+import {
+ anonUserCalloutsResponse,
+ userCalloutMutationResponse,
+ userCalloutsResponse,
+} from './user_callout_dismisser_mock_data';
+
+Vue.use(VueApollo);
+
+const initialSlotProps = (changes = {}) => ({
+ dismiss: expect.any(Function),
+ isAnonUser: false,
+ isDismissed: false,
+ isLoadingQuery: true,
+ isLoadingMutation: false,
+ mutationError: null,
+ queryError: null,
+ shouldShowCallout: false,
+ ...changes,
+});
+
+describe('UserCalloutDismisser', () => {
+ let wrapper;
+
+ const MOCK_FEATURE_NAME = 'mock_feature_name';
+
+ // Query handlers
+ const successHandlerFactory = (dismissedCallouts = []) => async () =>
+ userCalloutsResponse(dismissedCallouts);
+ const anonUserHandler = async () => anonUserCalloutsResponse();
+ const errorHandler = () => Promise.reject(new Error('query error'));
+ const pendingHandler = () => new Promise(() => {});
+
+ // Mutation handlers
+ const mutationSuccessHandlerSpy = jest.fn(async (variables) =>
+ userCalloutMutationResponse(variables),
+ );
+ const mutationErrorHandlerSpy = jest.fn(async (variables) =>
+ userCalloutMutationResponse(variables, ['mutation error']),
+ );
+
+ const defaultScopedSlotSpy = jest.fn();
+
+ const callDismissSlotProp = () => defaultScopedSlotSpy.mock.calls[0][0].dismiss();
+
+ const createComponent = ({ queryHandler, mutationHandler, ...options }) => {
+ wrapper = mount(
+ UserCalloutDismisser,
+ merge(
+ {
+ propsData: {
+ featureName: MOCK_FEATURE_NAME,
+ },
+ scopedSlots: {
+ default: defaultScopedSlotSpy,
+ },
+ apolloProvider: createMockApollo([
+ [getUserCalloutsQuery, queryHandler],
+ [dismissUserCalloutMutation, mutationHandler],
+ ]),
+ },
+ options,
+ ),
+ );
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when loading', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: pendingHandler,
+ });
+ });
+
+ it('passes expected slot props to child', () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(initialSlotProps());
+ });
+ });
+
+ describe('when loaded and dismissed', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: successHandlerFactory([MOCK_FEATURE_NAME]),
+ });
+
+ return waitForPromises();
+ });
+
+ it('passes expected slot props to child', () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isDismissed: true,
+ isLoadingQuery: false,
+ }),
+ );
+ });
+ });
+
+ describe('when loaded and not dismissed', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: successHandlerFactory(),
+ });
+
+ return waitForPromises();
+ });
+
+ it('passes expected slot props to child', () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isLoadingQuery: false,
+ shouldShowCallout: true,
+ }),
+ );
+ });
+ });
+
+ describe('when loaded with errors', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: errorHandler,
+ });
+
+ return waitForPromises();
+ });
+
+ it('passes expected slot props to child', () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isLoadingQuery: false,
+ queryError: expect.any(Error),
+ }),
+ );
+ });
+ });
+
+ describe('when loaded and the user is anonymous', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: anonUserHandler,
+ });
+
+ return waitForPromises();
+ });
+
+ it('passes expected slot props to child', () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isAnonUser: true,
+ isLoadingQuery: false,
+ }),
+ );
+ });
+ });
+
+ describe('when skipQuery is true', () => {
+ let queryHandler;
+ beforeEach(() => {
+ queryHandler = jest.fn();
+
+ createComponent({
+ queryHandler,
+ propsData: {
+ skipQuery: true,
+ },
+ });
+ });
+
+ it('does not run the query', async () => {
+ expect(queryHandler).not.toHaveBeenCalled();
+
+ await waitForPromises();
+
+ expect(queryHandler).not.toHaveBeenCalled();
+ });
+
+ it('passes expected slot props to child', () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isLoadingQuery: false,
+ shouldShowCallout: true,
+ }),
+ );
+ });
+ });
+
+ describe('dismissing', () => {
+ describe('given it succeeds', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: successHandlerFactory(),
+ mutationHandler: mutationSuccessHandlerSpy,
+ });
+
+ return waitForPromises();
+ });
+
+ it('dismissing calls mutation', () => {
+ expect(mutationSuccessHandlerSpy).not.toHaveBeenCalled();
+
+ callDismissSlotProp();
+
+ expect(mutationSuccessHandlerSpy).toHaveBeenCalledWith({
+ input: { featureName: MOCK_FEATURE_NAME },
+ });
+ });
+
+ it('passes expected slot props to child', async () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isLoadingQuery: false,
+ shouldShowCallout: true,
+ }),
+ );
+
+ callDismissSlotProp();
+
+ // Wait for Vue re-render due to prop change
+ await nextTick();
+
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isDismissed: true,
+ isLoadingMutation: true,
+ isLoadingQuery: false,
+ }),
+ );
+
+ // Wait for mutation to resolve
+ await waitForPromises();
+
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isDismissed: true,
+ isLoadingQuery: false,
+ }),
+ );
+ });
+ });
+
+ describe('given it fails', () => {
+ beforeEach(() => {
+ createComponent({
+ queryHandler: successHandlerFactory(),
+ mutationHandler: mutationErrorHandlerSpy,
+ });
+
+ return waitForPromises();
+ });
+
+ it('calls mutation', () => {
+ expect(mutationErrorHandlerSpy).not.toHaveBeenCalled();
+
+ callDismissSlotProp();
+
+ expect(mutationErrorHandlerSpy).toHaveBeenCalledWith({
+ input: { featureName: MOCK_FEATURE_NAME },
+ });
+ });
+
+ it('passes expected slot props to child', async () => {
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isLoadingQuery: false,
+ shouldShowCallout: true,
+ }),
+ );
+
+ callDismissSlotProp();
+
+ // Wait for Vue re-render due to prop change
+ await nextTick();
+
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isDismissed: true,
+ isLoadingMutation: true,
+ isLoadingQuery: false,
+ }),
+ );
+
+ // Wait for mutation to resolve
+ await waitForPromises();
+
+ expect(defaultScopedSlotSpy).lastCalledWith(
+ initialSlotProps({
+ isDismissed: true,
+ isLoadingQuery: false,
+ mutationError: ['mutation error'],
+ }),
+ );
+ });
+ });
+ });
+});
diff --git a/spec/lib/gitlab/import_export/shared_spec.rb b/spec/lib/gitlab/import_export/shared_spec.rb
index 22f2d4c5077..feeb88397eb 100644
--- a/spec/lib/gitlab/import_export/shared_spec.rb
+++ b/spec/lib/gitlab/import_export/shared_spec.rb
@@ -37,6 +37,28 @@ RSpec.describe Gitlab::ImportExport::Shared do
end
end
+ context 'with a group on disk' do
+ describe '#base_path' do
+ it 'uses hashed storage path' do
+ group = create(:group)
+ subject = described_class.new(group)
+ base_path = %(/tmp/gitlab_exports/@groups/)
+
+ expect(subject.base_path).to match(/#{base_path}\h{2}\/\h{2}\/\h{64}/)
+ end
+ end
+ end
+
+ context 'when exportable type is unsupported' do
+ describe '#base_path' do
+ it 'raises' do
+ subject = described_class.new('test')
+
+ expect { subject.base_path }.to raise_error(Gitlab::ImportExport::Error, 'Unsupported Exportable Type String')
+ end
+ end
+ end
+
describe '#error' do
let(:error) { StandardError.new('Error importing into /my/folder Permission denied @ unlink_internal - /var/opt/gitlab/gitlab-rails/shared/a/b/c/uploads/file') }
diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb
index 46a611852ab..f82c8da379f 100644
--- a/spec/models/import_export_upload_spec.rb
+++ b/spec/models/import_export_upload_spec.rb
@@ -24,4 +24,23 @@ RSpec.describe ImportExportUpload do
context 'export' do
it_behaves_like 'stores the Import/Export file', :export_file
end
+
+ describe 'scopes' do
+ let_it_be(:upload1) { create(:import_export_upload, export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz')) }
+ let_it_be(:upload2) { create(:import_export_upload) }
+ let_it_be(:upload3) { create(:import_export_upload, export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'), updated_at: 25.hours.ago) }
+ let_it_be(:upload4) { create(:import_export_upload, updated_at: 2.days.ago) }
+
+ describe '.with_export_file' do
+ it 'returns uploads with export file' do
+ expect(described_class.with_export_file).to contain_exactly(upload1, upload3)
+ end
+ end
+
+ describe '.updated_before' do
+ it 'returns uploads for a specified date' do
+ expect(described_class.updated_before(24.hours.ago)).to contain_exactly(upload3, upload4)
+ end
+ end
+ end
end
diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb
index 735f2225c21..2d7ee8ba3be 100644
--- a/spec/models/pages/lookup_path_spec.rb
+++ b/spec/models/pages/lookup_path_spec.rb
@@ -47,14 +47,7 @@ RSpec.describe Pages::LookupPath do
describe '#source' do
let(:source) { lookup_path.source }
- it 'uses disk storage', :aggregate_failures do
- expect(source[:type]).to eq('file')
- expect(source[:path]).to eq(project.full_path + "/public/")
- end
-
- it 'return nil when local storage is disabled and there is no deployment' do
- allow(Settings.pages.local_store).to receive(:enabled).and_return(false)
-
+ it 'returns nil' do
expect(source).to eq(nil)
end
diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb
index 4101b13adf9..2bcdfa6dd8f 100644
--- a/spec/services/import_export_clean_up_service_spec.rb
+++ b/spec/services/import_export_clean_up_service_spec.rb
@@ -8,7 +8,13 @@ RSpec.describe ImportExportCleanUpService do
let(:tmp_import_export_folder) { 'tmp/gitlab_exports' }
- context 'when the import/export directory does not exist' do
+ before do
+ allow_next_instance_of(Gitlab::Import::Logger) do |logger|
+ allow(logger).to receive(:info)
+ end
+ end
+
+ context 'when the import/export tmp storage directory does not exist' do
it 'does not remove any archives' do
path = '/invalid/path/'
stub_repository_downloads_path(path)
@@ -19,49 +25,84 @@ RSpec.describe ImportExportCleanUpService do
end
end
- context 'when the import/export directory exists' do
- it 'removes old files' do
- in_directory_with_files(mtime: 2.days.ago) do |dir, files|
- service.execute
-
- files.each { |file| expect(File.exist?(file)).to eq false }
- expect(File.directory?(dir)).to eq false
+ context 'when the import/export tmp storage directory exists' do
+ shared_examples 'removes old tmp files' do |subdir|
+ it 'removes old files and logs' do
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'Removed Import/Export tmp directory',
+ dir_path: anything
+ )
+ end
+
+ validate_cleanup(subdir: subdir, mtime: 2.days.ago, expected: false)
end
- end
- it 'does not remove new files' do
- in_directory_with_files(mtime: 2.hours.ago) do |dir, files|
- service.execute
+ it 'does not remove new files or logs' do
+ expect(Gitlab::Import::Logger).not_to receive(:new)
- files.each { |file| expect(File.exist?(file)).to eq true }
- expect(File.directory?(dir)).to eq true
+ validate_cleanup(subdir: subdir, mtime: 2.hours.ago, expected: true)
end
end
+
+ include_examples 'removes old tmp files', '@hashed'
+ include_examples 'removes old tmp files', '@groups'
end
context 'with uploader exports' do
- it 'removes old files' do
+ it 'removes old files and logs' do
upload = create(:import_export_upload,
updated_at: 2.days.ago,
export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'))
+ expect_next_instance_of(Gitlab::Import::Logger) do |logger|
+ expect(logger)
+ .to receive(:info)
+ .with(
+ message: 'Removed Import/Export export_file',
+ project_id: upload.project_id,
+ group_id: upload.group_id
+ )
+ end
+
expect { service.execute }.to change { upload.reload.export_file.file.nil? }.to(true)
+
+ expect(ImportExportUpload.where(export_file: nil)).to include(upload)
end
- it 'does not remove new files' do
+ it 'does not remove new files or logs' do
upload = create(:import_export_upload,
updated_at: 1.hour.ago,
export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'))
+ expect(Gitlab::Import::Logger).not_to receive(:new)
+
expect { service.execute }.not_to change { upload.reload.export_file.file.nil? }
+
+ expect(ImportExportUpload.where.not(export_file: nil)).to include(upload)
+ end
+ end
+
+ def validate_cleanup(subdir:, mtime:, expected:)
+ in_directory_with_files(mtime: mtime, subdir: subdir) do |dir, files|
+ service.execute
+
+ files.each { |file| expect(File.exist?(file)).to eq(expected) }
+ expect(File.directory?(dir)).to eq(expected)
end
end
- def in_directory_with_files(mtime:)
+ def in_directory_with_files(mtime:, subdir:)
Dir.mktmpdir do |tmpdir|
stub_repository_downloads_path(tmpdir)
- dir = File.join(tmpdir, tmp_import_export_folder, 'subfolder')
+ hashed = Digest::SHA2.hexdigest(subdir)
+ subdir_path = [subdir, hashed[0..1], hashed[2..3], hashed, hashed[4..10]]
+ dir = File.join(tmpdir, tmp_import_export_folder, *[subdir_path])
+
FileUtils.mkdir_p(dir)
+ File.utime(mtime.to_i, mtime.to_i, dir)
files = FileUtils.touch(file_list(dir) + [dir], mtime: mtime.to_time)
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 72e52146baf..350d6080c85 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -371,7 +371,6 @@ RSpec.describe 'Every Sidekiq worker' do
'PipelineMetricsWorker' => 3,
'PipelineNotificationWorker' => 3,
'PipelineProcessWorker' => 3,
- 'PipelineUpdateWorker' => 3,
'PostReceive' => 3,
'ProcessCommitWorker' => 3,
'ProjectCacheWorker' => 3,