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>2023-02-22 21:07:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-22 21:07:44 +0300
commitd0f16d56f3716d4a60027eb261f12080094f8db3 (patch)
treef9ecf9f4bda6d761f612bc4a5efb701b7c6b3d2d /spec
parent68aa32736b50c3609348f3bf740b81a2dfd1fb25 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/approvals/mock_data.js10
-rw-r--r--spec/frontend/fixtures/merge_requests.rb6
-rw-r--r--spec/frontend/search/sidebar/components/checkbox_filter_spec.js9
-rw-r--r--spec/frontend/search/sidebar/components/language_filter_spec.js (renamed from spec/frontend/search/sidebar/components/language_filters_spec.js)21
-rw-r--r--spec/frontend/search/store/actions_spec.js22
-rw-r--r--spec/frontend/search/store/getters_spec.js37
-rw-r--r--spec/frontend/search/store/utils_spec.js13
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js215
-rw-r--r--spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js14
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js3
-rw-r--r--spec/lib/gitlab/legacy_github_import/importer_spec.rb31
-rw-r--r--spec/models/namespace_spec.rb23
-rw-r--r--spec/requests/api/graphql/mutations/ci/runner/create_spec.rb121
-rw-r--r--spec/services/ci/runners/create_runner_service_spec.rb43
-rw-r--r--spec/services/import/validate_remote_git_endpoint_service_spec.rb24
-rw-r--r--spec/tooling/danger/sidekiq_args_spec.rb125
16 files changed, 510 insertions, 207 deletions
diff --git a/spec/frontend/approvals/mock_data.js b/spec/frontend/approvals/mock_data.js
new file mode 100644
index 00000000000..e0e90c09791
--- /dev/null
+++ b/spec/frontend/approvals/mock_data.js
@@ -0,0 +1,10 @@
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
+
+export const createCanApproveResponse = () => {
+ const response = JSON.parse(JSON.stringify(approvedByCurrentUser));
+ response.data.project.mergeRequest.userPermissions.canApprove = true;
+ response.data.project.mergeRequest.approved = false;
+ response.data.project.mergeRequest.approvedBy.nodes = [];
+
+ return response;
+};
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 7ee89ca3694..b6f6d149756 100644
--- a/spec/frontend/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -151,7 +151,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'merge request with no approvals' do
base_input_path = 'vue_merge_request_widget/components/approvals/queries/'
base_output_path = 'graphql/merge_requests/approvals/'
- query_name = 'approved_by.query.graphql'
+ query_name = 'approvals.query.graphql'
it "#{base_output_path}#{query_name}_no_approvals.json" do
query = get_graphql_query_as_string("#{base_input_path}#{query_name}", ee: Gitlab.ee?)
@@ -165,7 +165,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'merge request approved by current user' do
base_input_path = 'vue_merge_request_widget/components/approvals/queries/'
base_output_path = 'graphql/merge_requests/approvals/'
- query_name = 'approved_by.query.graphql'
+ query_name = 'approvals.query.graphql'
it "#{base_output_path}#{query_name}.json" do
merge_request.approved_by_users << user
@@ -181,7 +181,7 @@ RSpec.describe Projects::MergeRequestsController, '(JavaScript fixtures)', type:
context 'merge request approved by multiple users' do
base_input_path = 'vue_merge_request_widget/components/approvals/queries/'
base_output_path = 'graphql/merge_requests/approvals/'
- query_name = 'approved_by.query.graphql'
+ query_name = 'approvals.query.graphql'
it "#{base_output_path}#{query_name}_multiple_users.json" do
merge_request.approved_by_users << user
diff --git a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
index 82017754b23..e2a3fdeeb25 100644
--- a/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
+++ b/spec/frontend/search/sidebar/components/checkbox_filter_spec.js
@@ -17,8 +17,12 @@ describe('CheckboxFilter', () => {
setQuery: jest.fn(),
};
+ const getterSpies = {
+ queryLangugageFilters: jest.fn(() => []),
+ };
+
const defaultProps = {
- filterData: convertFiltersData(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
+ filtersData: convertFiltersData(MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
};
const createComponent = () => {
@@ -27,6 +31,7 @@ describe('CheckboxFilter', () => {
query: MOCK_QUERY,
},
actions: actionSpies,
+ getters: getterSpies,
});
wrapper = shallowMountExtended(CheckboxFilter, {
@@ -73,7 +78,7 @@ describe('CheckboxFilter', () => {
describe('actions', () => {
it('triggers setQuery', () => {
const filter =
- defaultProps.filterData.filters[Object.keys(defaultProps.filterData.filters)[0]].value;
+ defaultProps.filtersData.filters[Object.keys(defaultProps.filtersData.filters)[0]].value;
findFormCheckboxGroup().vm.$emit('input', filter);
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
diff --git a/spec/frontend/search/sidebar/components/language_filters_spec.js b/spec/frontend/search/sidebar/components/language_filter_spec.js
index e297d1c33b0..6870e759110 100644
--- a/spec/frontend/search/sidebar/components/language_filters_spec.js
+++ b/spec/frontend/search/sidebar/components/language_filter_spec.js
@@ -23,6 +23,7 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
const getterSpies = {
langugageAggregationBuckets: jest.fn(() => MOCK_LANGUAGE_AGGREGATIONS_BUCKETS),
+ queryLangugageFilters: jest.fn(() => []),
};
const createComponent = (initialState) => {
@@ -48,6 +49,7 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
const findForm = () => wrapper.findComponent(GlForm);
const findCheckboxFilter = () => wrapper.findComponent(CheckboxFilter);
const findApplyButton = () => wrapper.findByTestId('apply-button');
+ const findResetButton = () => wrapper.findByTestId('reset-button');
const findShowMoreButton = () => wrapper.findByTestId('show-more-button');
const findAlert = () => wrapper.findComponent(GlAlert);
const findAllCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox);
@@ -84,6 +86,25 @@ describe('GlobalSearchSidebarLanguageFilter', () => {
});
});
+ describe('resetButton', () => {
+ describe.each`
+ description | sidebarDirty | queryFilters | isDisabled
+ ${'sidebar dirty only'} | ${true} | ${[]} | ${undefined}
+ ${'query filters only'} | ${false} | ${['JSON', 'C']} | ${undefined}
+ ${'sidebar dirty and query filters'} | ${true} | ${['JSON', 'C']} | ${undefined}
+ ${'no sidebar and no query filters'} | ${false} | ${[]} | ${'true'}
+ `('$description', ({ sidebarDirty, queryFilters, isDisabled }) => {
+ beforeEach(() => {
+ getterSpies.queryLangugageFilters = jest.fn(() => queryFilters);
+ createComponent({ sidebarDirty, query: { ...MOCK_QUERY, language: queryFilters } });
+ });
+
+ it(`button is ${isDisabled ? 'enabled' : 'disabled'}`, () => {
+ expect(findResetButton().attributes('disabled')).toBe(isDisabled);
+ });
+ });
+ });
+
describe('ApplyButton', () => {
describe('when sidebarDirty is false', () => {
beforeEach(() => {
diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js
index 2f87802dfe6..0f270ff2491 100644
--- a/spec/frontend/search/store/actions_spec.js
+++ b/spec/frontend/search/store/actions_spec.js
@@ -325,4 +325,26 @@ describe('Global Search Store Actions', () => {
});
});
});
+
+ describe('resetLanguageQueryWithRedirect', () => {
+ it('calls visitUrl and setParams with the state.query', () => {
+ return testAction(actions.resetLanguageQueryWithRedirect, null, state, [], [], () => {
+ expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ ...state.query, page: null });
+ expect(urlUtils.visitUrl).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('resetLanguageQuery', () => {
+ it('calls commit SET_QUERY with value []', () => {
+ state = { ...state, query: { ...state.query, language: ['YAML', 'Text', 'Markdown'] } };
+ return testAction(
+ actions.resetLanguageQuery,
+ null,
+ state,
+ [{ type: types.SET_QUERY, payload: { key: 'language', value: [] } }],
+ [],
+ );
+ });
+ });
});
diff --git a/spec/frontend/search/store/getters_spec.js b/spec/frontend/search/store/getters_spec.js
index 818902ee720..b70a6be8d46 100644
--- a/spec/frontend/search/store/getters_spec.js
+++ b/spec/frontend/search/store/getters_spec.js
@@ -1,12 +1,14 @@
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants';
import * as getters from '~/search/store/getters';
import createState from '~/search/store/state';
+import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import {
MOCK_QUERY,
MOCK_GROUPS,
MOCK_PROJECTS,
MOCK_AGGREGATIONS,
MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
+ TEST_FILTER_DATA,
} from '../mock_data';
describe('Global Search Store Getters', () => {
@@ -14,37 +16,48 @@ describe('Global Search Store Getters', () => {
beforeEach(() => {
state = createState({ query: MOCK_QUERY });
+ useMockLocationHelper();
});
describe('frequentGroups', () => {
- beforeEach(() => {
- state.frequentItems[GROUPS_LOCAL_STORAGE_KEY] = MOCK_GROUPS;
- });
-
it('returns the correct data', () => {
+ state.frequentItems[GROUPS_LOCAL_STORAGE_KEY] = MOCK_GROUPS;
expect(getters.frequentGroups(state)).toStrictEqual(MOCK_GROUPS);
});
});
describe('frequentProjects', () => {
- beforeEach(() => {
- state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY] = MOCK_PROJECTS;
- });
-
it('returns the correct data', () => {
+ state.frequentItems[PROJECTS_LOCAL_STORAGE_KEY] = MOCK_PROJECTS;
expect(getters.frequentProjects(state)).toStrictEqual(MOCK_PROJECTS);
});
});
describe('langugageAggregationBuckets', () => {
- beforeEach(() => {
- state.aggregations.data = MOCK_AGGREGATIONS;
- });
-
it('returns the correct data', () => {
+ state.aggregations.data = MOCK_AGGREGATIONS;
expect(getters.langugageAggregationBuckets(state)).toStrictEqual(
MOCK_LANGUAGE_AGGREGATIONS_BUCKETS,
);
});
});
+
+ describe('queryLangugageFilters', () => {
+ it('returns the correct data', () => {
+ state.query.language = Object.keys(TEST_FILTER_DATA.filters);
+ expect(getters.queryLangugageFilters(state)).toStrictEqual(state.query.language);
+ });
+ });
+
+ describe('currentUrlQueryHasLanguageFilters', () => {
+ it.each`
+ description | lang | result
+ ${'has valid language'} | ${{ language: ['a', 'b'] }} | ${true}
+ ${'has empty lang'} | ${{ language: [] }} | ${false}
+ ${'has no lang'} | ${{}} | ${false}
+ `('$description', ({ lang, result }) => {
+ state.urlQuery = lang;
+ expect(getters.currentUrlQueryHasLanguageFilters(state)).toBe(result);
+ });
+ });
});
diff --git a/spec/frontend/search/store/utils_spec.js b/spec/frontend/search/store/utils_spec.js
index 487ed7bfe03..8c4a17f0a5d 100644
--- a/spec/frontend/search/store/utils_spec.js
+++ b/spec/frontend/search/store/utils_spec.js
@@ -226,11 +226,14 @@ describe('Global Search Store Utils', () => {
});
describe.each`
- description | currentQuery | urlQuery | isDirty
- ${'identical'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${false}
- ${'different'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'new' }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${true}
- ${'null/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: null, [SIDEBAR_PARAMS[1]]: null }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined }} | ${false}
- ${'updated/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: 'new', [SIDEBAR_PARAMS[1]]: 'new' }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined }} | ${true}
+ description | currentQuery | urlQuery | isDirty
+ ${'identical'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${false}
+ ${'different'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'new', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default', [SIDEBAR_PARAMS[2]]: ['a', 'c'] }} | ${true}
+ ${'null/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: null, [SIDEBAR_PARAMS[1]]: null, [SIDEBAR_PARAMS[2]]: null }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined, [SIDEBAR_PARAMS[2]]: undefined }} | ${false}
+ ${'updated/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: 'new', [SIDEBAR_PARAMS[1]]: 'new', [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined, [SIDEBAR_PARAMS[2]]: [] }} | ${true}
+ ${'language only no url params'} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[2]]: undefined }} | ${true}
+ ${'language only url params symetric'} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${false}
+ ${'language only url params asymetric'} | ${{ [SIDEBAR_PARAMS[2]]: ['a'] }} | ${{ [SIDEBAR_PARAMS[2]]: ['a', 'b'] }} | ${true}
`('isSidebarDirty', ({ description, currentQuery, urlQuery, isDirty }) => {
describe(`with ${description} sidebar query data`, () => {
let res;
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
index bf208f16d18..80a7565cbee 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_spec.js
@@ -1,17 +1,24 @@
-import { nextTick } from 'vue';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import { GlButton, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { createAlert } from '~/flash';
import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
import ApprovalsSummaryOptional from '~/vue_merge_request_widget/components/approvals/approvals_summary_optional.vue';
import {
- FETCH_LOADING,
- FETCH_ERROR,
APPROVE_ERROR,
UNAPPROVE_ERROR,
} from '~/vue_merge_request_widget/components/approvals/messages';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql';
+import { createCanApproveResponse } from 'jest/approvals/mock_data';
+
+Vue.use(VueApollo);
const mockAlertDismiss = jest.fn();
jest.mock('~/flash', () => ({
@@ -20,7 +27,6 @@ jest.mock('~/flash', () => ({
})),
}));
-const RULE_NAME = 'first_rule';
const TEST_HELP_PATH = 'help/path';
const testApprovedBy = () => [1, 7, 10].map((id) => ({ id }));
const testApprovals = () => ({
@@ -34,15 +40,18 @@ const testApprovals = () => ({
require_password_to_approve: false,
invalid_approvers_rules: [],
});
-const testApprovalRulesResponse = () => ({ rules: [{ id: 2 }] });
describe('MRWidget approvals', () => {
let wrapper;
let service;
let mr;
- const createComponent = (props = {}) => {
+ const createComponent = (props = {}, response = approvedByCurrentUser) => {
+ const requestHandlers = [[approvedByQuery, jest.fn().mockResolvedValue(response)]];
+ const apolloProvider = createMockApollo(requestHandlers);
+
wrapper = shallowMount(Approvals, {
+ apolloProvider,
propsData: {
mr,
service,
@@ -68,15 +77,10 @@ describe('MRWidget approvals', () => {
};
const findSummary = () => wrapper.findComponent(ApprovalsSummary);
const findOptionalSummary = () => wrapper.findComponent(ApprovalsSummaryOptional);
- const findInvalidRules = () => wrapper.find('[data-testid="invalid-rules"]');
beforeEach(() => {
service = {
...{
- fetchApprovals: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
- fetchApprovalSettings: jest
- .fn()
- .mockReturnValue(Promise.resolve(testApprovalRulesResponse())),
approveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
unapproveMergeRequest: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
approveMergeRequestWithAuth: jest.fn().mockReturnValue(Promise.resolve(testApprovals())),
@@ -97,55 +101,21 @@ describe('MRWidget approvals', () => {
};
jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
- });
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- describe('when created', () => {
- it('shows loading message', async () => {
- service = {
- fetchApprovals: jest.fn().mockReturnValue(new Promise(() => {})),
- };
-
- createComponent();
- await nextTick();
- expect(wrapper.text()).toContain(FETCH_LOADING);
- });
-
- it('fetches approvals', () => {
- createComponent();
- expect(service.fetchApprovals).toHaveBeenCalled();
- });
- });
-
- describe('when fetch approvals error', () => {
- beforeEach(() => {
- jest.spyOn(service, 'fetchApprovals').mockReturnValue(Promise.reject());
- createComponent();
- return nextTick();
- });
-
- it('still shows loading message', () => {
- expect(wrapper.text()).toContain(FETCH_LOADING);
- });
-
- it('flashes error', () => {
- expect(createAlert).toHaveBeenCalledWith({ message: FETCH_ERROR });
- });
+ gon.current_user_id = getIdFromGraphQLId(
+ approvedByCurrentUser.data.project.mergeRequest.approvedBy.nodes[0].id,
+ );
});
describe('action button', () => {
describe('when mr is closed', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ const response = createCanApproveResponse();
+
mr.isOpen = false;
- mr.approvals.user_has_approved = false;
- mr.approvals.user_can_approve = true;
- createComponent();
- return nextTick();
+ createComponent({}, response);
+ await waitForPromises();
});
it('action is not rendered', () => {
@@ -154,12 +124,12 @@ describe('MRWidget approvals', () => {
});
describe('when user cannot approve', () => {
- beforeEach(() => {
- mr.approvals.user_has_approved = false;
- mr.approvals.user_can_approve = false;
+ beforeEach(async () => {
+ const response = JSON.parse(JSON.stringify(approvedByCurrentUser));
+ response.data.project.mergeRequest.approvedBy.nodes = [];
- createComponent();
- return nextTick();
+ createComponent({}, response);
+ await waitForPromises();
});
it('action is not rendered', () => {
@@ -168,15 +138,16 @@ describe('MRWidget approvals', () => {
});
describe('when user can approve', () => {
+ let canApproveResponse;
+
beforeEach(() => {
- mr.approvals.user_has_approved = false;
- mr.approvals.user_can_approve = true;
+ canApproveResponse = createCanApproveResponse();
});
describe('and MR is unapproved', () => {
- beforeEach(() => {
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ createComponent({}, canApproveResponse);
+ await waitForPromises();
});
it('approve action is rendered', () => {
@@ -190,30 +161,33 @@ describe('MRWidget approvals', () => {
describe('and MR is approved', () => {
beforeEach(() => {
- mr.approvals.approved = true;
+ canApproveResponse.data.project.mergeRequest.approved = true;
});
describe('with no approvers', () => {
- beforeEach(() => {
- mr.approvals.approved_by = [];
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ canApproveResponse.data.project.mergeRequest.approvedBy.nodes = [];
+ createComponent({}, canApproveResponse);
+ await nextTick();
});
- it('approve action (with inverted style) is rendered', () => {
- expect(findActionData()).toEqual({
+ it('approve action is rendered', () => {
+ expect(findActionData()).toMatchObject({
variant: 'confirm',
text: 'Approve',
- category: 'secondary',
});
});
});
describe('with approvers', () => {
- beforeEach(() => {
- mr.approvals.approved_by = [{ user: { id: 7 } }];
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ canApproveResponse.data.project.mergeRequest.approvedBy.nodes =
+ approvedByCurrentUser.data.project.mergeRequest.approvedBy.nodes;
+
+ canApproveResponse.data.project.mergeRequest.approvedBy.nodes[0].id = 2;
+
+ createComponent({}, canApproveResponse);
+ await waitForPromises();
});
it('approve additionally action is rendered', () => {
@@ -227,9 +201,9 @@ describe('MRWidget approvals', () => {
});
describe('when approve action is clicked', () => {
- beforeEach(() => {
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ createComponent({}, canApproveResponse);
+ await waitForPromises();
});
it('shows loading icon', () => {
@@ -258,10 +232,6 @@ describe('MRWidget approvals', () => {
it('emits to eventHub', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
});
-
- it('calls store setApprovals', () => {
- expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
- });
});
describe('and error', () => {
@@ -286,12 +256,12 @@ describe('MRWidget approvals', () => {
});
describe('when user has approved', () => {
- beforeEach(() => {
- mr.approvals.user_has_approved = true;
- mr.approvals.user_can_approve = false;
+ beforeEach(async () => {
+ const response = JSON.parse(JSON.stringify(approvedByCurrentUser));
- createComponent();
- return nextTick();
+ createComponent({}, response);
+
+ await waitForPromises();
});
it('revoke action is rendered', () => {
@@ -316,10 +286,6 @@ describe('MRWidget approvals', () => {
it('emits to eventHub', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
});
-
- it('calls store setApprovals', () => {
- expect(mr.setApprovals).toHaveBeenCalledWith(testApprovals());
- });
});
describe('and error', () => {
@@ -338,19 +304,24 @@ describe('MRWidget approvals', () => {
});
describe('approvals optional summary', () => {
+ let optionalApprovalsResponse;
+
+ beforeEach(() => {
+ optionalApprovalsResponse = JSON.parse(JSON.stringify(approvedByCurrentUser));
+ });
+
describe('when no approvals required and no approvers', () => {
beforeEach(() => {
- mr.approvals.approved_by = [];
- mr.approvals.approvals_required = 0;
- mr.approvals.user_has_approved = false;
+ optionalApprovalsResponse.data.project.mergeRequest.approvedBy.nodes = [];
+ optionalApprovalsResponse.data.project.mergeRequest.approvalsRequired = 0;
});
describe('and can approve', () => {
- beforeEach(() => {
- mr.approvals.user_can_approve = true;
+ beforeEach(async () => {
+ optionalApprovalsResponse.data.project.mergeRequest.userPermissions.canApprove = true;
- createComponent();
- return nextTick();
+ createComponent({}, optionalApprovalsResponse);
+ await waitForPromises();
});
it('is shown', () => {
@@ -363,11 +334,9 @@ describe('MRWidget approvals', () => {
});
describe('and cannot approve', () => {
- beforeEach(() => {
- mr.approvals.user_can_approve = false;
-
- createComponent();
- return nextTick();
+ beforeEach(async () => {
+ createComponent({}, optionalApprovalsResponse);
+ await nextTick();
});
it('is shown', () => {
@@ -382,9 +351,9 @@ describe('MRWidget approvals', () => {
});
describe('approvals summary', () => {
- beforeEach(() => {
+ beforeEach(async () => {
createComponent();
- return nextTick();
+ await nextTick();
});
it('is rendered with props', () => {
@@ -393,41 +362,7 @@ describe('MRWidget approvals', () => {
expect(findOptionalSummary().exists()).toBe(false);
expect(summary.exists()).toBe(true);
expect(summary.props()).toMatchObject({
- projectPath: 'gitlab-org/gitlab',
- iid: '1',
- updatedCount: 0,
- });
- });
- });
-
- describe('invalid rules', () => {
- beforeEach(() => {
- mr.approvals.merge_request_approvers_available = true;
- createComponent();
- });
-
- it('does not render related components', () => {
- expect(findInvalidRules().exists()).toBe(false);
- });
-
- describe('when invalid rules are present', () => {
- beforeEach(() => {
- mr.approvals.invalid_approvers_rules = [{ name: RULE_NAME }];
- createComponent();
- });
-
- it('renders related components', () => {
- const invalidRules = findInvalidRules();
-
- expect(invalidRules.exists()).toBe(true);
-
- const invalidRulesText = invalidRules.text();
-
- expect(invalidRulesText).toContain(RULE_NAME);
- expect(invalidRulesText).toContain(
- 'GitLab has approved this rule automatically to unblock the merge request.',
- );
- expect(invalidRulesText).toContain('Learn more.');
+ approvalState: approvedByCurrentUser.data.project.mergeRequest,
});
});
});
diff --git a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
index e75ce7c60c9..62eddebd9c1 100644
--- a/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/approvals/approvals_summary_spec.js
@@ -1,11 +1,10 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mount } from '@vue/test-utils';
-import approvedByMultipleUsers from 'test_fixtures/graphql/merge_requests/approvals/approved_by.query.graphql_multiple_users.json';
-import noApprovalsResponse from 'test_fixtures/graphql/merge_requests/approvals/approved_by.query.graphql_no_approvals.json';
-import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approved_by.query.graphql.json';
+import approvedByMultipleUsers from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql_multiple_users.json';
+import noApprovalsResponse from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql_no_approvals.json';
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
import waitForPromises from 'helpers/wait_for_promises';
-import createMockApollo from 'helpers/mock_apollo_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import ApprovalsSummary from '~/vue_merge_request_widget/components/approvals/approvals_summary.vue';
import {
@@ -14,7 +13,6 @@ import {
APPROVED_BY_YOU_AND_OTHERS,
} from '~/vue_merge_request_widget/components/approvals/messages';
import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue';
-import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql';
Vue.use(VueApollo);
@@ -22,13 +20,11 @@ describe('MRWidget approvals summary', () => {
const originalUserId = gon.current_user_id;
let wrapper;
- const createComponent = (response = approvedByCurrentUser) => {
+ const createComponent = (data = approvedByCurrentUser) => {
wrapper = mount(ApprovalsSummary, {
propsData: {
- projectPath: 'gitlab-org/gitlab',
- iid: '1',
+ approvalState: data.data.project.mergeRequest,
},
- apolloProvider: createMockApollo([[approvedByQuery, jest.fn().mockResolvedValue(response)]]),
});
};
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index f37276ad594..608d513e64b 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -6,6 +6,7 @@ import VueApollo from 'vue-apollo';
import * as Sentry from '@sentry/browser';
import getStateQueryResponse from 'test_fixtures/graphql/merge_requests/get_state.query.graphql.json';
import readyToMergeResponse from 'test_fixtures/graphql/merge_requests/states/ready_to_merge.query.graphql.json';
+import approvedByCurrentUser from 'test_fixtures/graphql/merge_requests/approvals/approvals.query.graphql.json';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data';
@@ -28,6 +29,7 @@ import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_
import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql';
import getStateQuery from '~/vue_merge_request_widget/queries/get_state.query.graphql';
import readyToMergeQuery from 'ee_else_ce/vue_merge_request_widget/queries/states/ready_to_merge.query.graphql';
+import approvalsQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql';
import userPermissionsQuery from '~/vue_merge_request_widget/queries/permissions.query.graphql';
import conflictsStateQuery from '~/vue_merge_request_widget/queries/states/conflicts.query.graphql';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
@@ -100,6 +102,7 @@ describe('MrWidgetOptions', () => {
...options,
apolloProvider: createMockApollo([
+ [approvalsQuery, jest.fn().mockResolvedValue(approvedByCurrentUser)],
[
getStateQuery,
jest.fn().mockResolvedValue({
diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
index cd66b93eb8b..bb38f4b1bca 100644
--- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb
@@ -2,7 +2,9 @@
require 'spec_helper'
-RSpec.describe Gitlab::LegacyGithubImport::Importer do
+RSpec.describe Gitlab::LegacyGithubImport::Importer, feature_category: :importers do
+ subject(:importer) { described_class.new(project) }
+
shared_examples 'Gitlab::LegacyGithubImport::Importer#execute' do
let(:expected_not_called) { [] }
@@ -11,8 +13,6 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
end
it 'calls import methods' do
- importer = described_class.new(project)
-
expected_called = [
:import_labels, :import_milestones, :import_pull_requests, :import_issues,
:import_wiki, :import_releases, :handle_errors,
@@ -51,11 +51,13 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
- allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
+ allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request_missing_source_branch])
allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_raise(Octokit::NotFound)
allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
+
+ allow(importer).to receive(:restore_source_branch).and_raise(StandardError, 'Some error')
end
let(:label1) do
@@ -153,8 +155,6 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
}
end
- subject { described_class.new(project) }
-
it 'returns true' do
expect(subject.execute).to eq true
end
@@ -163,18 +163,19 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
expect { subject.execute }.not_to raise_error
end
- it 'stores error messages' do
+ it 'stores error messages', :unlimited_max_formatted_output_length do
error = {
message: 'The remote data could not be fully imported.',
errors: [
{ type: :label, url: "#{api_root}/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
+ { type: :pull_request, url: "#{api_root}/repos/octocat/Hello-World/pulls/1347", errors: 'Some error' },
{ type: :issue, url: "#{api_root}/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" },
{ type: :issues_comments, errors: 'Octokit::NotFound' },
{ type: :wiki, errors: "Gitlab::Git::CommandError" }
]
}
- described_class.new(project).execute
+ importer.execute
expect(project.import_state.last_error).to eq error.to_json
end
@@ -182,8 +183,6 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
shared_examples 'Gitlab::LegacyGithubImport unit-testing' do
describe '#clean_up_restored_branches' do
- subject { described_class.new(project) }
-
before do
allow(gh_pull_request).to receive(:source_branch_exists?).at_least(:once) { false }
allow(gh_pull_request).to receive(:target_branch_exists?).at_least(:once) { false }
@@ -240,6 +239,16 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
}
end
+ let(:pull_request_missing_source_branch) do
+ pull_request.merge(
+ head: {
+ ref: 'missing',
+ repo: repository,
+ sha: RepoHelpers.another_sample_commit
+ }
+ )
+ end
+
let(:closed_pull_request) do
{
number: 1347,
@@ -264,8 +273,6 @@ RSpec.describe Gitlab::LegacyGithubImport::Importer do
let(:api_root) { 'https://try.gitea.io/api/v1' }
let(:repo_root) { 'https://try.gitea.io' }
- subject { described_class.new(project) }
-
before do
project.update!(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
end
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 6c991f14156..4e3d8f633b7 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -571,17 +571,6 @@ RSpec.describe Namespace, feature_category: :subgroups do
it { expect(child.traversal_ids).to eq [parent.id, child.id] }
it { expect(parent.sync_events.count).to eq 1 }
it { expect(child.sync_events.count).to eq 1 }
-
- context 'when set_traversal_ids_on_save feature flag is disabled' do
- before do
- stub_feature_flags(set_traversal_ids_on_save: false)
- end
-
- it 'only sets traversal_ids on reload' do
- expect { parent.reload }.to change(parent, :traversal_ids).from([]).to([parent.id])
- expect { child.reload }.to change(child, :traversal_ids).from([]).to([parent.id, child.id])
- end
- end
end
context 'traversal_ids on update' do
@@ -594,18 +583,6 @@ RSpec.describe Namespace, feature_category: :subgroups do
it 'sets the traversal_ids attribute' do
expect { subject }.to change { namespace1.traversal_ids }.from([namespace1.id]).to([namespace2.id, namespace1.id])
end
-
- context 'when set_traversal_ids_on_save feature flag is disabled' do
- before do
- stub_feature_flags(set_traversal_ids_on_save: false)
- end
-
- it 'sets traversal_ids after reload' do
- subject
-
- expect { namespace1.reload }.to change(namespace1, :traversal_ids).from([]).to([namespace2.id, namespace1.id])
- end
- end
end
it 'creates a Namespaces::SyncEvent using triggers' do
diff --git a/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb b/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb
new file mode 100644
index 00000000000..bd22cc6706a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'RunnerCreate', feature_category: :runner_fleet do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:admin) { create(:admin) }
+
+ let(:mutation_params) do
+ {
+ description: 'create description',
+ maintenance_note: 'create maintenance note',
+ maximum_timeout: 900,
+ access_level: 'REF_PROTECTED',
+ paused: true,
+ run_untagged: false,
+ tag_list: %w[tag1 tag2]
+ }
+ end
+
+ let(:mutation) do
+ variables = {
+ **mutation_params
+ }
+
+ graphql_mutation(
+ :runner_create,
+ variables,
+ <<-QL
+ runner {
+ ephemeralAuthenticationToken
+
+ runnerType
+ description
+ maintenanceNote
+ paused
+ tagList
+ accessLevel
+ locked
+ maximumTimeout
+ runUntagged
+ }
+ errors
+ QL
+ )
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:runner_create) }
+
+ context 'when user does not have permissions' do
+ let(:current_user) { user }
+
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).to contain_exactly "Insufficient permissions"
+ end
+ end
+
+ context 'when user has permissions', :enable_admin_mode do
+ let(:current_user) { admin }
+
+ context 'when :create_runner_workflow feature flag is disabled' do
+ before do
+ stub_feature_flags(create_runner_workflow: false)
+ end
+
+ it 'returns an error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).not_to be_empty
+ expect(graphql_errors[0]['message'])
+ .to eq("`create_runner_workflow` feature flag is disabled.")
+ end
+ end
+
+ context 'when success' do
+ it do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+
+ mutation_params.each_key do |key|
+ expect(mutation_response['runner'][key.to_s.camelize(:lower)]).to eq mutation_params[key]
+ end
+
+ expect(mutation_response['runner']['ephemeralAuthenticationToken']).to start_with 'glrt'
+
+ expect(mutation_response['errors']).to eq([])
+ end
+ end
+
+ context 'when failure' do
+ let(:mutation_params) do
+ {
+ description: "",
+ maintenanceNote: "",
+ paused: true,
+ accessLevel: "NOT_PROTECTED",
+ runUntagged: false,
+ tagList:
+ [],
+ maximumTimeout: 1
+ }
+ end
+
+ it do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+
+ expect(mutation_response['errors']).to contain_exactly(
+ "Tags list can not be empty when runner is not allowed to pick untagged jobs",
+ "Maximum timeout needs to be at least 10 minutes"
+ )
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/runners/create_runner_service_spec.rb b/spec/services/ci/runners/create_runner_service_spec.rb
index 673bf3ef90e..52acfcbb7af 100644
--- a/spec/services/ci/runners/create_runner_service_spec.rb
+++ b/spec/services/ci/runners/create_runner_service_spec.rb
@@ -83,6 +83,49 @@ RSpec.describe ::Ci::Runners::CreateRunnerService, "#execute", feature_category:
expect(runner.authenticated_user_registration_type?).to be_truthy
expect(runner.runner_type).to eq 'instance_type'
end
+
+ context 'with a nil paused value' do
+ let(:args) do
+ {
+ paused: nil,
+ description: 'some description',
+ maintenance_note: 'a note',
+ tag_list: %w[tag1 tag2],
+ access_level: 'ref_protected',
+ locked: true,
+ maximum_timeout: 600,
+ run_untagged: false
+ }
+ end
+
+ it { is_expected.to be_success }
+
+ it 'creates runner with active set to true' do
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.active).to eq true
+ end
+ end
+
+ context 'with no paused value given' do
+ let(:args) do
+ {
+ description: 'some description',
+ maintenance_note: 'a note',
+ tag_list: %w[tag1 tag2],
+ access_level: 'ref_protected',
+ locked: true,
+ maximum_timeout: 600,
+ run_untagged: false
+ }
+ end
+
+ it { is_expected.to be_success }
+
+ it 'creates runner with active set to true' do
+ expect(runner).to be_an_instance_of(::Ci::Runner)
+ expect(runner.active).to eq true
+ end
+ end
end
end
diff --git a/spec/services/import/validate_remote_git_endpoint_service_spec.rb b/spec/services/import/validate_remote_git_endpoint_service_spec.rb
index 221ac2cd73a..1d2b3975832 100644
--- a/spec/services/import/validate_remote_git_endpoint_service_spec.rb
+++ b/spec/services/import/validate_remote_git_endpoint_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Import::ValidateRemoteGitEndpointService do
+RSpec.describe Import::ValidateRemoteGitEndpointService, feature_category: :importers do
include StubRequests
let_it_be(:base_url) { 'http://demo.host/path' }
@@ -35,6 +35,28 @@ RSpec.describe Import::ValidateRemoteGitEndpointService do
end
end
+ context 'when uri is using an invalid protocol' do
+ subject { described_class.new(url: 'ssh://demo.host/repo') }
+
+ it 'reports error when invalid URL is provided' do
+ result = subject.execute
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ end
+ end
+
+ context 'when uri is invalid' do
+ subject { described_class.new(url: 'http:example.com') }
+
+ it 'reports error when invalid URL is provided' do
+ result = subject.execute
+
+ expect(result).to be_a(ServiceResponse)
+ expect(result.error?).to be(true)
+ end
+ end
+
context 'when receiving HTTP response' do
subject { described_class.new(url: base_url) }
diff --git a/spec/tooling/danger/sidekiq_args_spec.rb b/spec/tooling/danger/sidekiq_args_spec.rb
new file mode 100644
index 00000000000..bfa9ef169de
--- /dev/null
+++ b/spec/tooling/danger/sidekiq_args_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'rspec-parameterized'
+require 'gitlab-dangerfiles'
+require 'danger'
+require 'danger/plugins/internal/helper'
+require 'gitlab/dangerfiles/spec_helper'
+
+require_relative '../../../tooling/danger/sidekiq_args'
+require_relative '../../../tooling/danger/project_helper'
+
+RSpec.describe Tooling::Danger::SidekiqArgs, feature_category: :tooling do
+ include_context "with dangerfile"
+
+ let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
+ let(:fake_project_helper) { Tooling::Danger::ProjectHelper }
+
+ subject(:specs) { fake_danger.new(helper: fake_helper) }
+
+ before do
+ allow(specs).to receive(:project_helper).and_return(fake_project_helper)
+ end
+
+ describe '#args_changed?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:before, :after, :result) do
+ " - def perform" | " + def perform(abc)" | true
+ " - def perform" | " + def perform(abc)" | true
+ " - def perform(abc)" | " + def perform(def)" | true
+ " - def perform(abc, def)" | " + def perform(abc)" | true
+ " - def perform(abc, def)" | " + def perform(def, abc)" | true
+ " - def perform" | " - def perform" | false
+ " + def perform" | " + def perform" | false
+ " - def perform(abc)" | " - def perform(abc)" | false
+ " + def perform(abc)" | " + def perform(abc)" | false
+ " - def perform(abc)" | " + def perform_foo(abc)" | false
+ end
+
+ with_them do
+ it 'returns correct result' do
+ expect(specs.args_changed?([before, after])).to eq(result)
+ end
+ end
+ end
+
+ describe '#add_comment_for_matched_line' do
+ let(:filename) { 'app/workers/hello_worker.rb' }
+ let(:file_lines) do
+ [
+ "Module Worker",
+ " def perform",
+ " puts hello world",
+ " end",
+ "end"
+ ]
+ end
+
+ before do
+ allow(specs.project_helper).to receive(:file_lines).and_return(file_lines)
+ end
+
+ context 'when args are changed' do
+ before do
+ allow(specs.helper).to receive(:changed_lines).and_return([" - def perform", " + def perform(abc)"])
+ allow(specs).to receive(:args_changed?).and_return(true)
+ end
+
+ it 'adds suggestion at the correct lines' do
+ expect(specs).to receive(:markdown).with(format(described_class::SUGGEST_MR_COMMENT), file: filename, line: 2)
+
+ specs.add_comment_for_matched_line(filename)
+ end
+ end
+
+ context 'when args are not changed' do
+ before do
+ allow(specs.helper).to receive(:changed_lines).and_return([" - def perform", " - def perform"])
+ allow(specs).to receive(:args_changed?).and_return(false)
+ end
+
+ it 'does not add suggestion' do
+ expect(specs).not_to receive(:markdown)
+
+ specs.add_comment_for_matched_line(filename)
+ end
+ end
+ end
+
+ describe '#changed_worker_files' do
+ let(:base_expected_files) { %w[app/workers/a.rb app/workers/b.rb ee/app/workers/e.rb] }
+
+ before do
+ all_changed_files = %w[
+ app/workers/a.rb
+ app/workers/b.rb
+ ee/app/workers/e.rb
+ spec/foo_spec.rb
+ ee/spec/foo_spec.rb
+ spec/bar_spec.rb
+ ee/spec/bar_spec.rb
+ spec/zab_spec.rb
+ ee/spec/zab_spec.rb
+ ]
+
+ allow(specs.helper).to receive(:all_changed_files).and_return(all_changed_files)
+ end
+
+ it 'returns added, modified, and renamed_after files by default' do
+ expect(specs.changed_worker_files).to match_array(base_expected_files)
+ end
+
+ context 'with include_ee: :exclude' do
+ it 'returns spec files without EE-specific files' do
+ expect(specs.changed_worker_files(ee: :exclude)).not_to include(%w[ee/app/workers/e.rb])
+ end
+ end
+
+ context 'with include_ee: :only' do
+ it 'returns EE-specific spec files only' do
+ expect(specs.changed_worker_files(ee: :only)).to match_array(%w[ee/app/workers/e.rb])
+ end
+ end
+ end
+end