diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-07 21:09:45 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-07 21:09:45 +0300 |
commit | 413119517cca6a47f52d77b49ae3cab4cdaf9884 (patch) | |
tree | 046eb80cb92bb948cd49a99b7c34bf8dc6884c4f /spec | |
parent | 40b78ea2b6f5f0ef730c2cd811911be3449562e6 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
43 files changed, 928 insertions, 327 deletions
diff --git a/spec/benchmarks/banzai_benchmark.rb b/spec/benchmarks/banzai_benchmark.rb index 4cf079b2130..05c41eed889 100644 --- a/spec/benchmarks/banzai_benchmark.rb +++ b/spec/benchmarks/banzai_benchmark.rb @@ -54,9 +54,10 @@ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do context 'pipelines' do it 'benchmarks several pipelines' do - path = 'images/example.jpg' - gitaly_wiki_file = Gitlab::GitalyClient::WikiFile.new(path: path) - allow(wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(gitaly_wiki_file)) + name = 'example.jpg' + path = "images/#{name}" + blob = double(name: name, path: path, mime_type: 'image/jpeg', data: nil) + allow(wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(blob)) allow(wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } puts "\n--> Benchmarking Full, Wiki, and Plain pipelines\n" diff --git a/spec/controllers/registrations/welcome_controller_spec.rb b/spec/controllers/registrations/welcome_controller_spec.rb index d32c936b8c9..008259a8bfa 100644 --- a/spec/controllers/registrations/welcome_controller_spec.rb +++ b/spec/controllers/registrations/welcome_controller_spec.rb @@ -60,8 +60,10 @@ RSpec.describe Registrations::WelcomeController do end describe '#update' do + let(:email_opted_in) { '0' } + subject(:update) do - patch :update, params: { user: { role: 'software_developer', setup_for_company: 'false' } } + patch :update, params: { user: { role: 'software_developer', setup_for_company: 'false', email_opted_in: email_opted_in } } end context 'without a signed in user' do @@ -74,6 +76,24 @@ RSpec.describe Registrations::WelcomeController do end it { is_expected.to redirect_to(dashboard_projects_path)} + + context 'when the user opted in' do + let(:email_opted_in) { '1' } + + it 'sets the email_opted_in field' do + subject + + expect(controller.current_user.email_opted_in).to eq(true) + end + end + + context 'when the user opted out' do + it 'sets the email_opted_in field' do + subject + + expect(controller.current_user.email_opted_in).to eq(false) + end + end end end end diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb index e84b300a748..3208ad82c03 100644 --- a/spec/features/markdown/markdown_spec.rb +++ b/spec/features/markdown/markdown_spec.rb @@ -63,8 +63,8 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do end aggregate_failures 'parses fenced code blocks' do - expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.c') - expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.python') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.language-c') + expect(doc).to have_selector('pre.code.highlight.js-syntax-highlight.language-python') end aggregate_failures 'parses mermaid code block' do @@ -288,9 +288,10 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do @wiki = @feat.wiki @wiki_page = @feat.wiki_page - path = 'images/example.jpg' - gitaly_wiki_file = Gitlab::GitalyClient::WikiFile.new(path: path) - expect(@wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(gitaly_wiki_file)) + name = 'example.jpg' + path = "images/#{name}" + blob = double(name: name, path: path, mime_type: 'image/jpeg', data: nil) + expect(@wiki).to receive(:find_file).with(path, load_content: false).and_return(Gitlab::Git::WikiFile.new(blob)) allow(@wiki).to receive(:wiki_base_path) { '/namespace1/gitlabhq/wikis' } @html = markdown(@feat.raw_markdown, { pipeline: :wiki, wiki: @wiki, page_slug: @wiki_page.slug }) diff --git a/spec/features/registrations/welcome_spec.rb b/spec/features/registrations/welcome_spec.rb new file mode 100644 index 00000000000..74320b69f19 --- /dev/null +++ b/spec/features/registrations/welcome_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Welcome screen' do + let(:user) { create(:user) } + + before do + gitlab_sign_in(user) + + visit users_sign_up_welcome_path + end + + it 'shows the email opt in' do + select 'Software Developer', from: 'user_role' + check 'user_email_opted_in' + click_button 'Get started!' + + expect(user.reload.email_opted_in).to eq(true) + end +end diff --git a/spec/finders/repositories/branch_names_finder_spec.rb b/spec/finders/repositories/branch_names_finder_spec.rb new file mode 100644 index 00000000000..4d8bfcc0f20 --- /dev/null +++ b/spec/finders/repositories/branch_names_finder_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::BranchNamesFinder do + let(:project) { create(:project, :repository) } + + let(:branch_names_finder) { described_class.new(project.repository, search: 'conflict-*') } + + describe '#execute' do + subject(:execute) { branch_names_finder.execute } + + it 'filters branch names' do + expect(execute).to contain_exactly( + 'conflict-binary-file', + 'conflict-resolvable', + 'conflict-contains-conflict-markers', + 'conflict-missing-side', + 'conflict-start', + 'conflict-non-utf8', + 'conflict-too-large' + ) + end + end +end diff --git a/spec/fixtures/api/schemas/public_api/v4/user/public.json b/spec/fixtures/api/schemas/public_api/v4/user/public.json index faa126b65f2..ee848eda9ed 100644 --- a/spec/fixtures/api/schemas/public_api/v4/user/public.json +++ b/spec/fixtures/api/schemas/public_api/v4/user/public.json @@ -70,6 +70,7 @@ "can_create_group": { "type": "boolean" }, "can_create_project": { "type": "boolean" }, "two_factor_enabled": { "type": "boolean" }, - "external": { "type": "boolean" } + "external": { "type": "boolean" }, + "commit_email": { "type": "string" } } } diff --git a/spec/frontend/pipelines/pipeline_graph/mock_data.js b/spec/frontend/pipelines/pipeline_graph/mock_data.js index 339aac9f349..a79917bfd48 100644 --- a/spec/frontend/pipelines/pipeline_graph/mock_data.js +++ b/spec/frontend/pipelines/pipeline_graph/mock_data.js @@ -98,6 +98,42 @@ export const pipelineData = { ], }; +export const invalidNeedsData = { + stages: [ + { + name: 'build', + groups: [ + { + name: 'build_1', + jobs: [{ script: 'echo hello', stage: 'build' }], + }, + ], + }, + { + name: 'test', + groups: [ + { + name: 'test_1', + jobs: [{ script: 'yarn test', stage: 'test' }], + }, + { + name: 'test_2', + jobs: [{ script: 'yarn karma', stage: 'test' }], + }, + ], + }, + { + name: 'deploy', + groups: [ + { + name: 'deploy_1', + jobs: [{ script: 'yarn magick', stage: 'deploy', needs: ['invalid_job'] }], + }, + ], + }, + ], +}; + export const parallelNeedData = { stages: [ { diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js index aeb567a8869..6deec06e344 100644 --- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js +++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js @@ -1,11 +1,13 @@ import { GlAlert } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { CI_CONFIG_STATUS_INVALID, CI_CONFIG_STATUS_VALID } from '~/pipeline_editor/constants'; +import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue'; +import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue'; import { DRAW_FAILURE, EMPTY_PIPELINE_DATA, INVALID_CI_CONFIG } from '~/pipelines/constants'; -import { pipelineData, singleStageData } from './mock_data'; +import { invalidNeedsData, pipelineData, singleStageData } from './mock_data'; describe('pipeline graph component', () => { const defaultProps = { pipelineData }; @@ -16,19 +18,28 @@ describe('pipeline graph component', () => { propsData: { ...props, }, + stubs: { LinksLayer, LinksInner }, + data() { + return { + measurements: { + width: 1000, + height: 1000, + }, + }; + }, }); }; - const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]'); - const findAlert = () => wrapper.find(GlAlert); - const findAllStagePills = () => wrapper.findAll(StagePill); + const findAlert = () => wrapper.findComponent(GlAlert); + const findAllJobPills = () => wrapper.findAll(JobPill); const findAllStageBackgroundElements = () => wrapper.findAll('[data-testid="stage-background"]'); + const findAllStagePills = () => wrapper.findAllComponents(StagePill); + const findLinksLayer = () => wrapper.findComponent(LinksLayer); + const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]'); const findStageBackgroundElementAt = (index) => findAllStageBackgroundElements().at(index); - const findAllJobPills = () => wrapper.findAll(JobPill); afterEach(() => { wrapper.destroy(); - wrapper = null; }); describe('with no data', () => { @@ -36,7 +47,7 @@ describe('pipeline graph component', () => { wrapper = createComponent({ pipelineData: {} }); }); - it('renders an empty section', () => { + it('does not render the graph', () => { expect(wrapper.text()).toBe(wrapper.vm.$options.errorTexts[EMPTY_PIPELINE_DATA]); expect(findPipelineGraph().exists()).toBe(false); expect(findAllStagePills()).toHaveLength(0); @@ -74,10 +85,11 @@ describe('pipeline graph component', () => { describe('with error while rendering the links with needs', () => { beforeEach(() => { - wrapper = createComponent(); + wrapper = createComponent({ pipelineData: invalidNeedsData }); }); it('renders the error that link could not be drawn', () => { + expect(findLinksLayer().exists()).toBe(true); expect(findAlert().exists()).toBe(true); expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[DRAW_FAILURE]); }); diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap index fc51825f15b..c37f6415898 100644 --- a/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap +++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/ci_cd_analytics_area_chart_spec.js.snap @@ -21,7 +21,11 @@ exports[`CiCdAnalyticsAreaChart matches the snapshot 1`] = ` option="[object Object]" thresholds="" width="0" - /> + > + <template /> + + <template /> + </glareachart-stub> </div> </div> `; diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js index dc2f227b29c..fee78d3af94 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_conflicts_spec.js @@ -1,4 +1,3 @@ -import { GlPopover } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; import { removeBreakLine } from 'helpers/text_helper'; @@ -10,7 +9,6 @@ describe('MRWidgetConflicts', () => { let mergeRequestWidgetGraphql = null; const path = '/conflicts'; - const findPopover = () => wrapper.find(GlPopover); const findResolveButton = () => wrapper.findByTestId('resolve-conflicts-button'); const findMergeLocalButton = () => wrapper.findByTestId('merge-locally-button'); @@ -219,12 +217,8 @@ describe('MRWidgetConflicts', () => { }); }); - it('sets resolve button as disabled', () => { - expect(findResolveButton().attributes('disabled')).toBe('true'); - }); - - it('shows the popover', () => { - expect(findPopover().exists()).toBe(true); + it('should not allow you to resolve the conflicts', () => { + expect(findResolveButton().exists()).toBe(false); }); }); @@ -241,12 +235,9 @@ describe('MRWidgetConflicts', () => { }); }); - it('sets resolve button as disabled', () => { - expect(findResolveButton().attributes('disabled')).toBe(undefined); - }); - - it('does not show the popover', () => { - expect(findPopover().exists()).toBe(false); + it('should allow you to resolve the conflicts', () => { + expect(findResolveButton().text()).toContain('Resolve conflicts'); + expect(findResolveButton().attributes('href')).toEqual(TEST_HOST); }); }); }); diff --git a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js index 01f7f3d49c7..bc1545014d7 100644 --- a/spec/frontend/vue_shared/components/runner_instructions/mock_data.js +++ b/spec/frontend/vue_shared/components/runner_instructions/mock_data.js @@ -98,9 +98,21 @@ export const mockGraphqlInstructions = { data: { runnerSetup: { installInstructions: - "# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n", + '# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start', registerInstructions: - 'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz', + 'sudo gitlab-runner register --url http://gdk.test:3000/ --registration-token $REGISTRATION_TOKEN', + __typename: 'RunnerSetup', + }, + }, +}; + +export const mockGraphqlInstructionsWindows = { + data: { + runnerSetup: { + installInstructions: + '# Windows runner, then run\n.gitlab-runner.exe install\n.gitlab-runner.exe start', + registerInstructions: + './gitlab-runner.exe register --url http://gdk.test:3000/ --registration-token $REGISTRATION_TOKEN', __typename: 'RunnerSetup', }, }, diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js new file mode 100644 index 00000000000..4033c943b82 --- /dev/null +++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js @@ -0,0 +1,184 @@ +import { GlAlert, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import getRunnerPlatformsQuery from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql'; +import getRunnerSetupInstructionsQuery from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql'; +import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; + +import { + mockGraphqlRunnerPlatforms, + mockGraphqlInstructions, + mockGraphqlInstructionsWindows, +} from './mock_data'; + +const localVue = createLocalVue(); +localVue.use(VueApollo); + +describe('RunnerInstructionsModal component', () => { + let wrapper; + let fakeApollo; + let runnerPlatformsHandler; + let runnerSetupInstructionsHandler; + + const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); + const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAlert = () => wrapper.findComponent(GlAlert); + const findPlatformButtons = () => wrapper.findAllByTestId('platform-button'); + const findArchitectureDropdownItems = () => wrapper.findAllByTestId('architecture-dropdown-item'); + const findBinaryInstructions = () => wrapper.findByTestId('binary-instructions'); + const findRegisterCommand = () => wrapper.findByTestId('register-command'); + + const createComponent = () => { + const requestHandlers = [ + [getRunnerPlatformsQuery, runnerPlatformsHandler], + [getRunnerSetupInstructionsQuery, runnerSetupInstructionsHandler], + ]; + + fakeApollo = createMockApollo(requestHandlers); + + wrapper = extendedWrapper( + shallowMount(RunnerInstructionsModal, { + propsData: { + modalId: 'runner-instructions-modal', + }, + localVue, + apolloProvider: fakeApollo, + }), + ); + }; + + beforeEach(async () => { + runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms); + runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions); + + createComponent(); + + await nextTick(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should not show alert', () => { + expect(findAlert().exists()).toBe(false); + }); + + it('should contain a number of platforms buttons', () => { + expect(runnerPlatformsHandler).toHaveBeenCalledWith({}); + + const buttons = findPlatformButtons(); + + expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length); + }); + + it('should contain a number of dropdown items for the architecture options', () => { + expect(findArchitectureDropdownItems()).toHaveLength( + mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length, + ); + }); + + describe('should display default instructions', () => { + const { installInstructions, registerInstructions } = mockGraphqlInstructions.data.runnerSetup; + + it('runner instructions are requested', () => { + expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({ + platform: 'linux', + architecture: 'amd64', + }); + }); + + it('binary instructions are shown', () => { + const instructions = findBinaryInstructions().text(); + + expect(instructions).toBe(installInstructions); + }); + + it('register command is shown', () => { + const instructions = findRegisterCommand().text(); + + expect(instructions).toBe(registerInstructions); + }); + }); + + describe('after a platform and architecture are selected', () => { + const { + installInstructions, + registerInstructions, + } = mockGraphqlInstructionsWindows.data.runnerSetup; + + beforeEach(async () => { + runnerSetupInstructionsHandler.mockResolvedValue(mockGraphqlInstructionsWindows); + + findPlatformButtons().at(2).vm.$emit('click'); // another option, happens to be windows + await nextTick(); + + findArchitectureDropdownItems().at(1).vm.$emit('click'); // another option + await nextTick(); + }); + + it('runner instructions are requested', () => { + expect(runnerSetupInstructionsHandler).toHaveBeenCalledWith({ + platform: 'windows', + architecture: '386', + }); + }); + + it('other binary instructions are shown', () => { + const instructions = findBinaryInstructions().text(); + + expect(instructions).toBe(installInstructions); + }); + + it('register command is shown', () => { + const command = findRegisterCommand().text(); + + expect(command).toBe(registerInstructions); + }); + }); + + describe('when apollo is loading', () => { + it('should show a skeleton loader', async () => { + createComponent(); + expect(findSkeletonLoader().exists()).toBe(true); + expect(findGlLoadingIcon().exists()).toBe(false); + + await nextTick(); // wait for platforms + + expect(findGlLoadingIcon().exists()).toBe(true); + }); + + it('once loaded, should not show a loading state', async () => { + createComponent(); + + await nextTick(); // wait for platforms + await nextTick(); // wait for architectures + + expect(findSkeletonLoader().exists()).toBe(false); + expect(findGlLoadingIcon().exists()).toBe(false); + }); + }); + + describe('when instructions cannot be loaded', () => { + beforeEach(async () => { + runnerSetupInstructionsHandler.mockRejectedValue(); + + createComponent(); + + await waitForPromises(); + }); + + it('should show alert', () => { + expect(findAlert().exists()).toBe(true); + }); + + it('should not show instructions', () => { + expect(findBinaryInstructions().exists()).toBe(false); + expect(findRegisterCommand().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js index 4446c3cd88f..23f8d6afcb5 100644 --- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js +++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_spec.js @@ -1,147 +1,41 @@ -import { GlAlert } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql'; -import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql'; +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue'; - -import { mockGraphqlRunnerPlatforms, mockGraphqlInstructions } from './mock_data'; - -const projectPath = 'gitlab-org/gitlab'; -const localVue = createLocalVue(); -localVue.use(VueApollo); +import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; describe('RunnerInstructions component', () => { let wrapper; - let fakeApollo; - let runnerPlatformsHandler; - let runnerSetupInstructionsHandler; - const findAlert = () => wrapper.findComponent(GlAlert); - const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]'); - const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]'); - const findArchitectureDropdownItems = () => - wrapper.findAll('[data-testid="architecture-dropdown-item"]'); - const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]'); - const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]'); + const findModalButton = () => wrapper.findByTestId('show-modal-button'); + const findModal = () => wrapper.findComponent(RunnerInstructionsModal); const createComponent = () => { - const requestHandlers = [ - [getRunnerPlatforms, runnerPlatformsHandler], - [getRunnerSetupInstructions, runnerSetupInstructionsHandler], - ]; - - fakeApollo = createMockApollo(requestHandlers); - - wrapper = shallowMount(RunnerInstructions, { - provide: { - projectPath, - }, - localVue, - apolloProvider: fakeApollo, - }); + wrapper = extendedWrapper(shallowMount(RunnerInstructions)); }; - beforeEach(async () => { - runnerPlatformsHandler = jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms); - runnerSetupInstructionsHandler = jest.fn().mockResolvedValue(mockGraphqlInstructions); - + beforeEach(() => { createComponent(); - - await wrapper.vm.$nextTick(); }); afterEach(() => { wrapper.destroy(); - wrapper = null; - }); - - it('should not show alert', () => { - expect(findAlert().exists()).toBe(false); }); it('should show the "Show Runner installation instructions" button', () => { - const button = findModalButton(); - - expect(button.exists()).toBe(true); - expect(button.text()).toBe('Show Runner installation instructions'); - }); - - it('should contain a number of platforms buttons', () => { - const buttons = findPlatformButtons(); - - expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length); - }); - - it('should contain a number of dropdown items for the architecture options', () => { - const platformButton = findPlatformButtons().at(0); - platformButton.vm.$emit('click'); - - return wrapper.vm.$nextTick(() => { - const dropdownItems = findArchitectureDropdownItems(); - - expect(dropdownItems).toHaveLength( - mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length, - ); - }); + expect(findModalButton().exists()).toBe(true); + expect(findModalButton().text()).toBe('Show Runner installation instructions'); }); - it('should display the binary installation instructions for a selected architecture', async () => { - const platformButton = findPlatformButtons().at(0); - platformButton.vm.$emit('click'); - - await wrapper.vm.$nextTick(); - - const dropdownItem = findArchitectureDropdownItems().at(0); - dropdownItem.vm.$emit('click'); - - await wrapper.vm.$nextTick(); - - const runner = findBinaryInstructionsSection(); - - expect(runner.text()).toMatch('sudo chmod +x /usr/local/bin/gitlab-runner'); - expect(runner.text()).toMatch( - `sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`, - ); - expect(runner.text()).toMatch( - 'sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner', - ); - expect(runner.text()).toMatch('sudo gitlab-runner start'); + it('should not render the modal once mounted', () => { + expect(findModal().exists()).toBe(false); }); - it('should display the runner register instructions for a selected architecture', async () => { - const platformButton = findPlatformButtons().at(0); - platformButton.vm.$emit('click'); - - await wrapper.vm.$nextTick(); - - const dropdownItem = findArchitectureDropdownItems().at(0); - dropdownItem.vm.$emit('click'); - - await wrapper.vm.$nextTick(); - - const runner = findRunnerInstructionsSection(); - - expect(runner.text()).toMatch(mockGraphqlInstructions.data.runnerSetup.registerInstructions); - }); - - describe('when instructions cannot be loaded', () => { - beforeEach(async () => { - runnerSetupInstructionsHandler.mockRejectedValue(); - - createComponent(); - - await wrapper.vm.$nextTick(); - }); + it('should render the modal once clicked', async () => { + findModalButton().vm.$emit('click'); - it('should show alert', () => { - expect(findAlert().exists()).toBe(true); - }); + await nextTick(); - it('should not show instructions', () => { - expect(findBinaryInstructionsSection().exists()).toBe(false); - expect(findRunnerInstructionsSection().exists()).toBe(false); - }); + expect(findModal().exists()).toBe(true); }); }); diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index 09f5181107d..aec6c6c6708 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -189,6 +189,17 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end + context 'with negated label argument' do + let_it_be(:label) { merge_request_6.labels.first } + let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) } + + it 'excludes merge requests with given label from selection' do + result = resolve_mr(project, not: { labels: [label.title] }) + + expect(result).not_to include(merge_request_6, with_label) + end + end + context 'with merged_after and merged_before arguments' do before do merge_request_1.metrics.update!(merged_at: 10.days.ago) @@ -221,6 +232,14 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end + context 'with negated milestone argument' do + it 'filters out merge requests with given milestone title' do + result = resolve_mr(project, not: { milestone_title: milestone.title }) + + expect(result).not_to include(merge_request_with_milestone) + end + end + describe 'combinations' do it 'requires all filters' do create(:merge_request, :closed, **common_attrs, source_branch: merge_request_4.source_branch) diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 33f4953b07b..7a8c6464acc 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -243,6 +243,7 @@ RSpec.describe GitlabSchema.types['Project'] do :assignee_username, :reviewer_username, :milestone_title, + :not, :sort ) end diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 7fcd5ae880a..120dbe7cb49 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -121,27 +121,13 @@ RSpec.describe AvatarsHelper do end end - context "when :avatar_cache_for_email flag is enabled" do - before do - stub_feature_flags(avatar_cache_for_email: true) - end - - it_behaves_like "returns avatar for email" + it_behaves_like "returns avatar for email" - it "caches the request" do - expect(User).to receive(:find_by_any_email).once.and_call_original - - expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url) - expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url) - end - end - - context "when :avatar_cache_for_email flag is disabled" do - before do - stub_feature_flags(avatar_cache_for_email: false) - end + it "caches the request" do + expect(User).to receive(:find_by_any_email).once.and_call_original - it_behaves_like "returns avatar for email" + expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url) + expect(helper.avatar_icon_for_email(user.email).to_s).to eq(user.avatar.url) end end diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 91e9075f575..08a20e87f4b 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -575,7 +575,7 @@ FooBar it 'preserves code color scheme' do object = create_object("```ruby\ndef test\n 'hello world'\nend\n```") - expected = "<pre class=\"code highlight js-syntax-highlight ruby\">" \ + expected = "<pre class=\"code highlight js-syntax-highlight language-ruby\">" \ "<code><span class=\"line\"><span class=\"k\">def</span> <span class=\"nf\">test</span>...</span>\n" \ "</code></pre>" diff --git a/spec/initializers/active_record_locking_spec.rb b/spec/initializers/active_record_locking_spec.rb index e979fa0b793..735ef7b916b 100644 --- a/spec/initializers/active_record_locking_spec.rb +++ b/spec/initializers/active_record_locking_spec.rb @@ -11,13 +11,13 @@ RSpec.describe 'ActiveRecord locking' do end it 'can be updated' do - issue.update(title: "New title") + issue.update!(title: "New title") expect(issue.reload.lock_version).to eq(new_lock_version) end it 'can be deleted' do - expect { issue.destroy }.to change { Issue.count }.by(-1) + expect { issue.destroy! }.to change { Issue.count }.by(-1) end end diff --git a/spec/initializers/fog_google_https_private_urls_spec.rb b/spec/initializers/fog_google_https_private_urls_spec.rb index 4825525a3d8..f7b21bf850e 100644 --- a/spec/initializers/fog_google_https_private_urls_spec.rb +++ b/spec/initializers/fog_google_https_private_urls_spec.rb @@ -13,11 +13,13 @@ RSpec.describe 'Fog::Storage::GoogleXML::File', :fog_requests do end let(:file) do + # rubocop:disable Rails/SaveBang directory = storage.directories.create(key: 'data') directory.files.create( body: 'Hello World!', key: 'hello_world.txt' ) + # rubocop:enable Rails/SaveBang end it 'delegates to #get_https_url' do diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb index b0136ce1fef..23626576c0c 100644 --- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb +++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb @@ -16,12 +16,8 @@ RSpec.describe Banzai::Filter::GollumTagsFilter do context 'linking internal images' do it 'creates img tag if image exists' do - gollum_file_double = double('Gollum::File', - mime_type: 'image/jpeg', - name: 'images/image.jpg', - path: 'images/image.jpg', - raw_data: '') - wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double) + blob = double(mime_type: 'image/jpeg', name: 'images/image.jpg', path: 'images/image.jpg', data: '') + wiki_file = Gitlab::Git::WikiFile.new(blob) expect(wiki).to receive(:find_file).with('images/image.jpg', load_content: false).and_return(wiki_file) tag = '[[images/image.jpg]]' diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb index 9f6688f4f7d..6d22fa3a001 100644 --- a/spec/lib/banzai/filter/math_filter_spec.rb +++ b/spec/lib/banzai/filter/math_filter_spec.rb @@ -91,35 +91,35 @@ RSpec.describe Banzai::Filter::MathFilter do # Display math it 'adds data-math-style display attribute to display math' do - doc = filter('<pre class="code highlight js-syntax-highlight math" v-pre="true"><code>2+2</code></pre>') + doc = filter('<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>') pre = doc.xpath('descendant-or-self::pre').first expect(pre['data-math-style']).to eq 'display' end it 'adds js-render-math class to display math' do - doc = filter('<pre class="code highlight js-syntax-highlight math" v-pre="true"><code>2+2</code></pre>') + doc = filter('<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>') pre = doc.xpath('descendant-or-self::pre').first expect(pre[:class]).to include("js-render-math") end it 'ignores code blocks that are not math' do - input = '<pre class="code highlight js-syntax-highlight plaintext" v-pre="true"><code>2+2</code></pre>' + input = '<pre class="code highlight js-syntax-highlight language-plaintext" v-pre="true"><code>2+2</code></pre>' doc = filter(input) expect(doc.to_s).to eq input end it 'requires the pre to contain both code and math' do - input = '<pre class="highlight js-syntax-highlight plaintext math" v-pre="true"><code>2+2</code></pre>' + input = '<pre class="highlight js-syntax-highlight language-plaintext language-math" v-pre="true"><code>2+2</code></pre>' doc = filter(input) expect(doc.to_s).to eq input end it 'dollar signs around to display math' do - doc = filter('$<pre class="code highlight js-syntax-highlight math" v-pre="true"><code>2+2</code></pre>$') + doc = filter('$<pre class="code highlight js-syntax-highlight language-math" v-pre="true"><code>2+2</code></pre>$') before = doc.xpath('descendant-or-self::text()[1]').first after = doc.xpath('descendant-or-self::text()[3]').first diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb index 7d6092e21e9..d74bac4898e 100644 --- a/spec/lib/banzai/filter/suggestion_filter_spec.rb +++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Banzai::Filter::SuggestionFilter do include FilterSpecHelper - let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) } + let(:input) { %(<pre class="code highlight js-syntax-highlight language-suggestion"><code>foo\n</code></pre>) } let(:default_context) do { suggestions_filter_enabled: true } end @@ -26,7 +26,7 @@ RSpec.describe Banzai::Filter::SuggestionFilter do context 'multi-line suggestions' do let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR } - let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) } + let(:input) { %(<pre class="code highlight js-syntax-highlight language-suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) } it 'element has correct data-lang-params' do doc = filter(input, default_context) diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb index 78f84ee44f7..16e30604c99 100644 --- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb +++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb @@ -20,7 +20,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as plaintext" do result = filter('<pre><code>def fun end</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">def fun end</span></code></pre>') end include_examples "XSS prevention", "" @@ -38,7 +38,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as that language" do result = filter('<pre><code lang="ruby">def fun end</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight language-ruby" lang="ruby" v-pre="true"><code><span id="LC1" class="line" lang="ruby"><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></span></code></pre>') end include_examples "XSS prevention", "ruby" @@ -48,7 +48,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as plaintext" do result = filter('<pre><code lang="gnuplot">This is a test</code></pre>') - expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>') + expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">This is a test</span></code></pre>') end include_examples "XSS prevention", "gnuplot" @@ -63,7 +63,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "highlights as plaintext but with the correct language attribute and class" do result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>}) - expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) + expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) end include_examples "XSS prevention", lang @@ -75,7 +75,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "includes data-lang-params tag with extra information" do result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>}) - expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) + expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) end include_examples "XSS prevention", lang @@ -93,7 +93,7 @@ RSpec.describe Banzai::Filter::SyntaxHighlightFilter do it "delimits on the first appearance" do result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>}) - expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) + expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight language-#{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>}) end end end diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb index ab6093e9198..007d310247b 100644 --- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb @@ -297,7 +297,7 @@ RSpec.describe Banzai::Pipeline::WikiPipeline do mime_type: 'image/jpeg', name: 'images/image.jpg', path: 'images/image.jpg', - raw_data: '') + data: '') wiki_file = Gitlab::Git::WikiFile.new(gollum_file_double) markdown = "[[#{wiki_file.path}]]" diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 3eb015a5a22..f3799c58fed 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -83,7 +83,7 @@ module Gitlab }, 'fenced code with inline script' => { input: '```mypre"><script>alert(3)</script>', - output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"></span></code></pre>\n</div>\n</div>" + output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight language-plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"></span></code></pre>\n</div>\n</div>" } } @@ -353,7 +353,7 @@ module Gitlab output = <<~HTML <div> <div> - <pre class="code highlight js-syntax-highlight javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre> + <pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"><code><span id="LC1" class="line" lang="javascript"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span></span></code></pre> </div> </div> HTML @@ -380,7 +380,7 @@ module Gitlab <div> <div>class.cpp</div> <div> - <pre class="code highlight js-syntax-highlight cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include <stdio.h></span></span> + <pre class="code highlight js-syntax-highlight language-cpp" lang="cpp" v-pre="true"><code><span id="LC1" class="line" lang="cpp"><span class="cp">#include <stdio.h></span></span> <span id="LC2" class="line" lang="cpp"></span> <span id="LC3" class="line" lang="cpp"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span></span> <span id="LC4" class="line" lang="cpp"> <span class="n">std</span><span class="o">::</span><span class="n">cout</span><span class="o"><<</span><span class="s">"*"</span><span class="o"><<</span><span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span></span> diff --git a/spec/lib/gitlab/graphql/negatable_arguments_spec.rb b/spec/lib/gitlab/graphql/negatable_arguments_spec.rb new file mode 100644 index 00000000000..bc6e25eb018 --- /dev/null +++ b/spec/lib/gitlab/graphql/negatable_arguments_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Graphql::NegatableArguments do + let(:test_resolver) do + Class.new(Resolvers::BaseResolver).tap do |klass| + klass.extend described_class + allow(klass).to receive(:name).and_return('Resolvers::TestResolver') + end + end + + describe '#negated' do + it 'defines :not argument' do + test_resolver.negated {} + + expect(test_resolver.arguments['not'].type.name).to eq "Types::TestResolverNegatedParamsType" + end + + it 'defines any arguments passed as block' do + test_resolver.negated do + argument :foo, GraphQL::STRING_TYPE, required: false + end + + expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(['foo']) + end + + it 'defines all arguments passed as block even if called multiple times' do + test_resolver.negated do + argument :foo, GraphQL::STRING_TYPE, required: false + end + test_resolver.negated do + argument :bar, GraphQL::STRING_TYPE, required: false + end + + expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(%w[foo bar]) + end + + it 'allows to specify custom argument name' do + test_resolver.negated(param_key: :negative) {} + + expect(test_resolver.arguments).to include('negative') + end + end +end diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb index 89917e515d0..48e2a2e9794 100644 --- a/spec/lib/gitlab/profiler_spec.rb +++ b/spec/lib/gitlab/profiler_spec.rb @@ -78,13 +78,8 @@ RSpec.describe Gitlab::Profiler do end it 'strips out the private token' do - expect(custom_logger).to receive(:add) do |severity, _progname, message| - next if message.include?('spec/') - - expect(severity).to eq(Logger::DEBUG) - expect(message).to include('public').and include(described_class::FILTERED_STRING) - expect(message).not_to include(private_token) - end.at_least(1) # This spec could be wrapped in more blocks in the future + allow(custom_logger).to receive(:add).and_call_original + expect(custom_logger).to receive(:add).with(Logger::DEBUG, anything, 'public [FILTERED]').at_least(1) custom_logger.debug("public #{private_token}") end diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb index 6bdcc5ea2b7..eaecbb0233d 100644 --- a/spec/lib/gitlab/repository_set_cache_spec.rb +++ b/spec/lib/gitlab/repository_set_cache_spec.rb @@ -124,6 +124,18 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do end end + describe '#search' do + subject do + cache.search(:foo, 'val*') do + %w[value helloworld notvalmatch] + end + end + + it 'returns search pattern matches from the key' do + is_expected.to contain_exactly('value') + end + end + describe '#include?' do it 'checks inclusion in the Redis set' do cache.write(:foo, ['value']) diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index e7681ae5706..a948815171e 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -34,6 +34,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s 'source_code', 'incident_management', 'incident_management_alerts', + 'incident_management_oncall', 'testing', 'issues_edit', 'ci_secrets_management', diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 31066419ee4..d819f28e114 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -1362,7 +1362,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories } let(:ineligible_total_categories) do - %w[source_code ci_secrets_management incident_management_alerts snippets terraform] + %w[source_code ci_secrets_management incident_management_alerts snippets terraform incident_management_oncall] end context 'with redis_hll_tracking feature enabled' do diff --git a/spec/mailers/emails/in_product_marketing_spec.rb b/spec/mailers/emails/in_product_marketing_spec.rb index e4157eaf5dc..25735e64bdf 100644 --- a/spec/mailers/emails/in_product_marketing_spec.rb +++ b/spec/mailers/emails/in_product_marketing_spec.rb @@ -13,6 +13,38 @@ RSpec.describe Emails::InProductMarketing do describe '#in_product_marketing_email' do using RSpec::Parameterized::TableSyntax + let(:track) { :create } + let(:series) { 0 } + + subject { Notify.in_product_marketing_email(user.id, group.id, track, series) } + + include_context 'gitlab email notification' + + it 'sends to the right user with a link to unsubscribe' do + aggregate_failures do + expect(subject).to deliver_to(user.notification_email) + expect(subject).to have_body_text(profile_notifications_url) + end + end + + context 'when on gitlab.com' do + before do + allow(Gitlab).to receive(:com?).and_return(true) + end + + it 'has custom headers' do + aggregate_failures do + expect(subject).to deliver_from(described_class::FROM_ADDRESS) + expect(subject).to reply_to(described_class::FROM_ADDRESS) + expect(subject).to have_header('X-Mailgun-Track', 'yes') + expect(subject).to have_header('X-Mailgun-Track-Clicks', 'yes') + expect(subject).to have_header('X-Mailgun-Track-Opens', 'yes') + expect(subject).to have_header('X-Mailgun-Tag', 'marketing') + expect(subject).to have_body_text('%tag_unsubscribe_url%') + end + end + end + where(:track, :series) do :create | 0 :create | 1 @@ -29,8 +61,6 @@ RSpec.describe Emails::InProductMarketing do end with_them do - subject { Notify.in_product_marketing_email(user.id, group.id, track, series) } - it 'has the correct subject and content' do aggregate_failures do is_expected.to have_subject(subject_line(track, series)) diff --git a/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb b/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb new file mode 100644 index 00000000000..ddff9ce32b4 --- /dev/null +++ b/spec/models/concerns/cascading_namespace_setting_attribute_spec.rb @@ -0,0 +1,320 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe NamespaceSetting, 'CascadingNamespaceSettingAttribute' do + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + + def group_settings + group.namespace_settings + end + + def subgroup_settings + subgroup.namespace_settings + end + + describe '#delayed_project_removal' do + subject(:delayed_project_removal) { subgroup_settings.delayed_project_removal } + + context 'when the feature is disabled' do + before do + stub_feature_flags(cascading_namespace_settings: false) + + group_settings.update!(delayed_project_removal: true) + end + + it 'does not cascade' do + expect(delayed_project_removal).to eq(nil) + end + end + + context 'when there is no parent' do + context 'and the value is not nil' do + before do + group_settings.update!(delayed_project_removal: true) + end + + it 'returns the local value' do + expect(group_settings.delayed_project_removal).to eq(true) + end + end + + context 'and the value is nil' do + before do + group_settings.update!(delayed_project_removal: nil) + stub_application_setting(delayed_project_removal: false) + end + + it 'returns the application settings value' do + expect(group_settings.delayed_project_removal).to eq(false) + end + end + end + + context 'when parent does not lock the attribute' do + context 'and value is not nil' do + before do + group_settings.update!(delayed_project_removal: false) + end + + it 'returns local setting when present' do + subgroup_settings.update!(delayed_project_removal: true) + + expect(delayed_project_removal).to eq(true) + end + + it 'returns the parent value when local value is nil' do + subgroup_settings.update!(delayed_project_removal: nil) + + expect(delayed_project_removal).to eq(false) + end + + it 'returns the correct dirty value' do + subgroup_settings.delayed_project_removal = true + + expect(delayed_project_removal).to eq(true) + end + + it 'does not return the application setting value when parent value is false' do + stub_application_setting(delayed_project_removal: true) + + expect(delayed_project_removal).to eq(false) + end + end + + context 'and the value is nil' do + before do + group_settings.update!(delayed_project_removal: nil, lock_delayed_project_removal: false) + subgroup_settings.update!(delayed_project_removal: nil) + + subgroup_settings.clear_memoization(:delayed_project_removal) + end + + it 'cascades to the application settings value' do + expect(delayed_project_removal).to eq(false) + end + end + + context 'when multiple ancestors set a value' do + let(:third_level_subgroup) { create(:group, parent: subgroup) } + + before do + group_settings.update!(delayed_project_removal: true) + subgroup_settings.update!(delayed_project_removal: false) + end + + it 'returns the closest ancestor value' do + expect(third_level_subgroup.namespace_settings.delayed_project_removal).to eq(false) + end + end + end + + context 'when parent locks the attribute' do + before do + subgroup_settings.update!(delayed_project_removal: true) + group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false) + + subgroup_settings.clear_memoization(:delayed_project_removal) + subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor) + end + + it 'returns the parent value' do + expect(delayed_project_removal).to eq(false) + end + + it 'does not allow the local value to be saved' do + subgroup_settings.delayed_project_removal = nil + + expect { subgroup_settings.save! } + .to raise_error(ActiveRecord::RecordInvalid, /Delayed project removal cannot be changed because it is locked by an ancestor/) + end + end + + context 'when the application settings locks the attribute' do + before do + subgroup_settings.update!(delayed_project_removal: true) + stub_application_setting(lock_delayed_project_removal: true, delayed_project_removal: true) + end + + it 'returns the application setting value' do + expect(delayed_project_removal).to eq(true) + end + + it 'does not allow the local value to be saved' do + subgroup_settings.delayed_project_removal = nil + + expect { subgroup_settings.save! } + .to raise_error(ActiveRecord::RecordInvalid, /Delayed project removal cannot be changed because it is locked by an ancestor/) + end + end + end + + describe '#delayed_project_removal?' do + before do + subgroup_settings.update!(delayed_project_removal: true) + group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false) + + subgroup_settings.clear_memoization(:delayed_project_removal) + subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor) + end + + it 'aliases the method when the attribute is a boolean' do + expect(subgroup_settings.delayed_project_removal?).to eq(subgroup_settings.delayed_project_removal) + end + end + + describe '#delayed_project_removal_locked?' do + shared_examples 'not locked' do + it 'is not locked by an ancestor' do + expect(subgroup_settings.delayed_project_removal_locked_by_ancestor?).to eq(false) + end + + it 'is not locked by application setting' do + expect(subgroup_settings.delayed_project_removal_locked_by_application_setting?).to eq(false) + end + + it 'does not return a locked namespace' do + expect(subgroup_settings.delayed_project_removal_locked_ancestor).to be_nil + end + end + + context 'when the feature is disabled' do + before do + stub_feature_flags(cascading_namespace_settings: false) + + group_settings.update!(delayed_project_removal: true) + end + + it_behaves_like 'not locked' + end + + context 'when parent does not lock the attribute' do + it_behaves_like 'not locked' + end + + context 'when parent locks the attribute' do + before do + group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false) + + subgroup_settings.clear_memoization(:delayed_project_removal) + subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor) + end + + it 'is locked by an ancestor' do + expect(subgroup_settings.delayed_project_removal_locked_by_ancestor?).to eq(true) + end + + it 'is not locked by application setting' do + expect(subgroup_settings.delayed_project_removal_locked_by_application_setting?).to eq(false) + end + + it 'returns a locked namespace settings object' do + expect(subgroup_settings.delayed_project_removal_locked_ancestor.namespace_id).to eq(group_settings.namespace_id) + end + end + + context 'when not locked by application settings' do + before do + stub_application_setting(lock_delayed_project_removal: false) + end + + it_behaves_like 'not locked' + end + + context 'when locked by application settings' do + before do + stub_application_setting(lock_delayed_project_removal: true) + end + + it 'is not locked by an ancestor' do + expect(subgroup_settings.delayed_project_removal_locked_by_ancestor?).to eq(false) + end + + it 'is locked by application setting' do + expect(subgroup_settings.delayed_project_removal_locked_by_application_setting?).to eq(true) + end + + it 'does not return a locked namespace' do + expect(subgroup_settings.delayed_project_removal_locked_ancestor).to be_nil + end + end + end + + describe '#lock_delayed_project_removal=' do + context 'when parent locks the attribute' do + before do + group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false) + + subgroup_settings.clear_memoization(:delayed_project_removal) + subgroup_settings.clear_memoization(:delayed_project_removal_locked_ancestor) + end + + it 'does not allow the attribute to be saved' do + subgroup_settings.lock_delayed_project_removal = true + + expect { subgroup_settings.save! } + .to raise_error(ActiveRecord::RecordInvalid, /Lock delayed project removal cannot be changed because it is locked by an ancestor/) + end + end + + context 'when parent does not lock the attribute' do + before do + group_settings.update!(lock_delayed_project_removal: false) + + subgroup_settings.lock_delayed_project_removal = true + end + + it 'allows the lock to be set when the attribute is not nil' do + subgroup_settings.delayed_project_removal = true + + expect(subgroup_settings.save).to eq(true) + end + + it 'does not allow the lock to be saved when the attribute is nil' do + subgroup_settings.delayed_project_removal = nil + + expect { subgroup_settings.save! } + .to raise_error(ActiveRecord::RecordInvalid, /Delayed project removal cannot be nil when locking the attribute/) + end + end + + context 'when application settings locks the attribute' do + before do + stub_application_setting(lock_delayed_project_removal: true) + end + + it 'does not allow the attribute to be saved' do + subgroup_settings.lock_delayed_project_removal = true + + expect { subgroup_settings.save! } + .to raise_error(ActiveRecord::RecordInvalid, /Lock delayed project removal cannot be changed because it is locked by an ancestor/) + end + end + + context 'when application_settings does not lock the attribute' do + before do + stub_application_setting(lock_delayed_project_removal: false) + end + + it 'allows the attribute to be saved' do + subgroup_settings.delayed_project_removal = true + subgroup_settings.lock_delayed_project_removal = true + + expect(subgroup_settings.save).to eq(true) + end + end + end + + describe 'after update callback' do + before do + subgroup_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: false) + end + + it 'clears descendant locks' do + group_settings.update!(lock_delayed_project_removal: true, delayed_project_removal: true) + + expect(subgroup_settings.reload.lock_delayed_project_removal).to eq(false) + end + end +end diff --git a/spec/models/concerns/ci/has_status_spec.rb b/spec/models/concerns/ci/has_status_spec.rb index ea7dbf0411c..b16420bc658 100644 --- a/spec/models/concerns/ci/has_status_spec.rb +++ b/spec/models/concerns/ci/has_status_spec.rb @@ -197,12 +197,6 @@ RSpec.describe Ci::HasStatus do end end - describe '.completed_and_blocked_statuses' do - subject { Ci::Pipeline.completed_and_blocked_statuses } - - it { is_expected.to eq [:success, :failed, :canceled, :skipped, :manual, :scheduled] } - end - context 'for scope with one status' do shared_examples 'having a job' do |status| %i[ci_build generic_commit_status].each do |type| diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 02b266e4fae..71cfe3ff45b 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -16,7 +16,7 @@ RSpec.describe ChatMessage::MergeMessage do project_url: 'http://somewhere.com', object_attributes: { - title: "Merge Request title\nSecond line", + title: "Merge request title\nSecond line", id: 10, iid: 100, assignee_id: 1, @@ -35,7 +35,7 @@ RSpec.describe ChatMessage::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) opened merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>') + 'Test User (test.user) opened merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge request title*> in <http://somewhere.com|project_name>') expect(subject.attachments).to be_empty end end @@ -46,7 +46,7 @@ RSpec.describe ChatMessage::MergeMessage do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) closed merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> in <http://somewhere.com|project_name>') + 'Test User (test.user) closed merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge request title*> in <http://somewhere.com|project_name>') expect(subject.attachments).to be_empty end end @@ -60,12 +60,12 @@ RSpec.describe ChatMessage::MergeMessage do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) opened merge request [!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') + 'Test User (test.user) opened merge request [!100 *Merge request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ - title: 'Merge Request opened by Test User (test.user)', + title: 'Merge request opened by Test User (test.user)', subtitle: 'in [project_name](http://somewhere.com)', - text: '[!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100)', + text: '[!100 *Merge request title*](http://somewhere.com/-/merge_requests/100)', image: 'http://someavatar.com' }) end @@ -78,12 +78,12 @@ RSpec.describe ChatMessage::MergeMessage do it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) closed merge request [!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') + 'Test User (test.user) closed merge request [!100 *Merge request title*](http://somewhere.com/-/merge_requests/100) in [project_name](http://somewhere.com)') expect(subject.attachments).to be_empty expect(subject.activity).to eq({ - title: 'Merge Request closed by Test User (test.user)', + title: 'Merge request closed by Test User (test.user)', subtitle: 'in [project_name](http://somewhere.com)', - text: '[!100 *Merge Request title*](http://somewhere.com/-/merge_requests/100)', + text: '[!100 *Merge request title*](http://somewhere.com/-/merge_requests/100)', image: 'http://someavatar.com' }) end @@ -97,7 +97,7 @@ RSpec.describe ChatMessage::MergeMessage do it 'returns a message regarding completed approval of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) approved merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\ + 'Test User (test.user) approved merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge request title*> '\ 'in <http://somewhere.com|project_name>') expect(subject.attachments).to be_empty end @@ -110,7 +110,7 @@ RSpec.describe ChatMessage::MergeMessage do it 'returns a message regarding revocation of completed approval of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) unapproved merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\ + 'Test User (test.user) unapproved merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge request title*> '\ 'in <http://somewhere.com|project_name>') expect(subject.attachments).to be_empty end @@ -123,7 +123,7 @@ RSpec.describe ChatMessage::MergeMessage do it 'returns a message regarding added approval of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) added their approval to merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\ + 'Test User (test.user) added their approval to merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge request title*> '\ 'in <http://somewhere.com|project_name>') expect(subject.attachments).to be_empty end @@ -136,7 +136,7 @@ RSpec.describe ChatMessage::MergeMessage do it 'returns a message regarding revoking approval of merge requests' do expect(subject.pretext).to eq( - 'Test User (test.user) removed their approval from merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\ + 'Test User (test.user) removed their approval from merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge request title*> '\ 'in <http://somewhere.com|project_name>') expect(subject.attachments).to be_empty end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 84404a31f76..c1a292c9b30 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -170,6 +170,22 @@ RSpec.describe Repository do end end + describe '#search_branch_names' do + subject(:search_branch_names) { repository.search_branch_names('conflict-*') } + + it 'returns matching branch names' do + expect(search_branch_names).to contain_exactly( + 'conflict-binary-file', + 'conflict-resolvable', + 'conflict-contains-conflict-markers', + 'conflict-missing-side', + 'conflict-start', + 'conflict-non-utf8', + 'conflict-too-large' + ) + end + end + describe '#list_last_commits_for_tree' do let(:path_to_commit) do { diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ac92311e132..ee67afcd50b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2521,32 +2521,12 @@ RSpec.describe User do describe "#clear_avatar_caches" do let(:user) { create(:user) } - context "when :avatar_cache_for_email flag is enabled" do - before do - stub_feature_flags(avatar_cache_for_email: true) - end - - it "clears the avatar cache when saving" do - allow(user).to receive(:avatar_changed?).and_return(true) - - expect(Gitlab::AvatarCache).to receive(:delete_by_email).with(*user.verified_emails) - - user.update(avatar: fixture_file_upload('spec/fixtures/dk.png')) - end - end - - context "when :avatar_cache_for_email flag is disabled" do - before do - stub_feature_flags(avatar_cache_for_email: false) - end - - it "doesn't attempt to clear the avatar cache" do - allow(user).to receive(:avatar_changed?).and_return(true) + it "clears the avatar cache when saving" do + allow(user).to receive(:avatar_changed?).and_return(true) - expect(Gitlab::AvatarCache).not_to receive(:delete_by_email) + expect(Gitlab::AvatarCache).to receive(:delete_by_email).with(*user.verified_emails) - user.update(avatar: fixture_file_upload('spec/fixtures/dk.png')) - end + user.update(avatar: fixture_file_upload('spec/fixtures/dk.png')) end end diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb index c72fd71884e..36055779a2e 100644 --- a/spec/services/ci/pipeline_trigger_service_spec.rb +++ b/spec/services/ci/pipeline_trigger_service_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Ci::PipelineTriggerService do stub_ci_pipeline_to_return_yaml_file end - describe '#execute', :context_aware do + describe '#execute' do let_it_be(:user) { create(:user) } let(:result) { described_class.new(project, user, params).execute } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 84c38ca0ce2..a3925a0c0fb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -338,20 +338,10 @@ RSpec.configure do |config| RequestStore.clear! end - if ENV['SKIP_RSPEC_CONTEXT_WRAPPING'] - config.around(:example, :context_aware) do |example| - # Wrap each example in it's own context to make sure the contexts don't - # leak - Gitlab::ApplicationContext.with_raw_context { example.run } - end - else - config.around do |example| - if [:controller, :request, :feature].include?(example.metadata[:type]) || example.metadata[:context_aware] - Gitlab::ApplicationContext.with_raw_context { example.run } - else - example.run - end - end + config.around do |example| + # Wrap each example in it's own context to make sure the contexts don't + # leak + Gitlab::ApplicationContext.with_raw_context { example.run } end config.around do |example| diff --git a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb index c775ca182e6..d5ebda28f0a 100644 --- a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb +++ b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'API::CI::Runner application context metadata' do |api_route| - it 'contains correct context metadata', :context_aware do + it 'contains correct context metadata' do # Avoids popping the context from the thread so we can # check its content after the request. allow(Labkit::Context).to receive(:pop) diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index 50d50bee727..6b243aef3e6 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -354,33 +354,29 @@ RSpec.shared_examples 'wiki model' do subject.repository.create_file(user, 'image.png', image, branch_name: subject.default_branch, message: 'add image') end - shared_examples 'find_file results' do - it 'returns the latest version of the file if it exists' do - file = subject.find_file('image.png') + it 'returns the latest version of the file if it exists' do + file = subject.find_file('image.png') - expect(file.mime_type).to eq('image/png') - end + expect(file.mime_type).to eq('image/png') + end - it 'returns nil if the page does not exist' do - expect(subject.find_file('non-existent')).to eq(nil) - end + it 'returns nil if the page does not exist' do + expect(subject.find_file('non-existent')).to eq(nil) + end - it 'returns a Gitlab::Git::WikiFile instance' do - file = subject.find_file('image.png') + it 'returns a Gitlab::Git::WikiFile instance' do + file = subject.find_file('image.png') - expect(file).to be_a Gitlab::Git::WikiFile - end + expect(file).to be_a Gitlab::Git::WikiFile + end - it 'returns the whole file' do - file = subject.find_file('image.png') - image.rewind + it 'returns the whole file' do + file = subject.find_file('image.png') + image.rewind - expect(file.raw_data.b).to eq(image.read.b) - end + expect(file.raw_data.b).to eq(image.read.b) end - it_behaves_like 'find_file results' - context 'when load_content is disabled' do it 'includes the file data in the Gitlab::Git::WikiFile' do file = subject.find_file('image.png', load_content: false) @@ -388,14 +384,6 @@ RSpec.shared_examples 'wiki model' do expect(file.raw_data).to be_empty end end - - context 'when feature flag :gitaly_find_file is disabled' do - before do - stub_feature_flags(gitaly_find_file: false) - end - - it_behaves_like 'find_file results' - end end describe '#create_page' do diff --git a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb index 57e28e6df57..cb06c9fa596 100644 --- a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'storing arguments in the application context' do - it 'places the expected params in the application context', :context_aware do + it 'places the expected params in the application context' do # Stub the clearing of the context so we can validate it later allow(Labkit::Context).to receive(:pop) diff --git a/spec/views/registrations/welcome/show.html.haml_spec.rb b/spec/views/registrations/welcome/show.html.haml_spec.rb index d9774582545..639759ae095 100644 --- a/spec/views/registrations/welcome/show.html.haml_spec.rb +++ b/spec/views/registrations/welcome/show.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe 'registrations/welcome/show' do - using RSpec::Parameterized::TableSyntax + let(:is_gitlab_com) { false } let_it_be(:user) { User.new } @@ -13,7 +13,7 @@ RSpec.describe 'registrations/welcome/show' do allow(view).to receive(:in_trial_flow?).and_return(false) allow(view).to receive(:user_has_memberships?).and_return(false) allow(view).to receive(:in_oauth_flow?).and_return(false) - allow(Gitlab).to receive(:com?).and_return(false) + allow(Gitlab).to receive(:com?).and_return(is_gitlab_com) render end @@ -22,4 +22,24 @@ RSpec.describe 'registrations/welcome/show' do it { is_expected.not_to have_selector('label[for="user_setup_for_company"]') } it { is_expected.to have_button('Get started!') } + it { is_expected.to have_selector('input[name="user[email_opted_in]"]') } + + describe 'email opt in' do + context 'when on gitlab.com' do + let(:is_gitlab_com) { true } + + it 'hides the email-opt in by default' do + expect(subject).to have_css('.js-email-opt-in.hidden') + end + end + + context 'when not on gitlab.com' do + let(:is_gitlab_com) { false } + + it 'hides the email-opt in by default' do + expect(subject).not_to have_css('.js-email-opt-in.hidden') + expect(subject).to have_css('.js-email-opt-in') + end + end + end end diff --git a/spec/workers/namespaces/in_product_marketing_emails_worker_spec.rb b/spec/workers/namespaces/in_product_marketing_emails_worker_spec.rb index 24143e8cf8a..af4a6dac6cd 100644 --- a/spec/workers/namespaces/in_product_marketing_emails_worker_spec.rb +++ b/spec/workers/namespaces/in_product_marketing_emails_worker_spec.rb @@ -3,45 +3,49 @@ require 'spec_helper' RSpec.describe Namespaces::InProductMarketingEmailsWorker, '#perform' do - context 'when the application setting is enabled' do + using RSpec::Parameterized::TableSyntax + + RSpec.shared_examples 'in-product marketing email' do before do - stub_application_setting(in_product_marketing_emails_enabled: true) + stub_application_setting(in_product_marketing_emails_enabled: in_product_marketing_emails_enabled) + stub_experiment(in_product_marketing_emails: experiment_active) + allow(::Gitlab).to receive(:com?).and_return(is_gitlab_com) end - context 'when the experiment is inactive' do - before do - stub_experiment(in_product_marketing_emails: false) - end - - it 'does not execute the in product marketing emails service' do - expect(Namespaces::InProductMarketingEmailsService).not_to receive(:send_for_all_tracks_and_intervals) + it 'executes the email service service' do + expect(Namespaces::InProductMarketingEmailsService).to receive(:send_for_all_tracks_and_intervals).exactly(executes_service).times - subject.perform - end + subject.perform end + end - context 'when the experiment is active' do - before do - stub_experiment(in_product_marketing_emails: true) - end + context 'not on gitlab.com' do + let(:is_gitlab_com) { false } - it 'calls the send_for_all_tracks_and_intervals method on the in product marketing emails service' do - expect(Namespaces::InProductMarketingEmailsService).to receive(:send_for_all_tracks_and_intervals) + where(:in_product_marketing_emails_enabled, :experiment_active, :executes_service) do + true | true | 1 + true | false | 1 + false | false | 0 + false | true | 0 + end - subject.perform - end + with_them do + include_examples 'in-product marketing email' end end - context 'when the application setting is disabled' do - before do - stub_application_setting(in_product_marketing_emails_enabled: false) - end + context 'on gitlab.com' do + let(:is_gitlab_com) { true } - it 'does not execute the in product marketing emails service' do - expect(Namespaces::InProductMarketingEmailsService).not_to receive(:send_for_all_tracks_and_intervals) + where(:in_product_marketing_emails_enabled, :experiment_active, :executes_service) do + true | true | 1 + true | false | 0 + false | false | 0 + false | true | 0 + end - subject.perform + with_them do + include_examples 'in-product marketing email' end end end |