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>2022-07-01 00:09:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-01 00:09:49 +0300
commit9877050db1dd1693c672a6b29a356c5b2a7edce0 (patch)
tree07e8b3837333294337121b96f296eb5622a8a7e9 /spec
parent370736438075748c36abd7fd7dd32a8ef98048f9 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb12
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/factories/error_tracking/error.rb2
-rw-r--r--spec/factories/project_error_tracking_settings.rb1
-rw-r--r--spec/finders/ci/runner_jobs_finder_spec.rb53
-rw-r--r--spec/finders/packages/conan/package_finder_spec.rb51
-rw-r--r--spec/fixtures/api/schemas/entities/member.json2
-rw-r--r--spec/fixtures/api/schemas/entities/member_user_default.json35
-rw-r--r--spec/fixtures/api/schemas/entities/member_user_for_admin_member.json (renamed from spec/fixtures/api/schemas/entities/member_user.json)0
-rw-r--r--spec/frontend/notebook/cells/markdown_spec.js2
-rw-r--r--spec/frontend/projects/settings/access_dropdown_spec.js17
-rw-r--r--spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js52
-rw-r--r--spec/graphql/resolvers/ci/config_resolver_spec.rb100
-rw-r--r--spec/helpers/integrations_helper_spec.rb15
-rw-r--r--spec/helpers/projects_helper_spec.rb1
-rw-r--r--spec/helpers/timeboxes_helper_spec.rb19
-rw-r--r--spec/initializers/net_http_response_patch_spec.rb79
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb26
-rw-r--r--spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb21
-rw-r--r--spec/lib/gitlab/buffered_io_spec.rb64
-rw-r--r--spec/lib/gitlab/ci/runner_upgrade_check_spec.rb9
-rw-r--r--spec/lib/gitlab/github_import/importer/events/closed_spec.rb72
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb77
-rw-r--r--spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb128
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_event_spec.rb111
-rw-r--r--spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb29
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb59
-rw-r--r--spec/lib/gitlab/version_info_spec.rb122
-rw-r--r--spec/models/application_setting_spec.rb8
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb77
-rw-r--r--spec/models/user_spec.rb35
-rw-r--r--spec/policies/project_policy_spec.rb21
-rw-r--r--spec/requests/api/ci/runners_spec.rb17
-rw-r--r--spec/requests/api/labels_spec.rb61
-rw-r--r--spec/requests/jira_routing_spec.rb54
-rw-r--r--spec/serializers/member_user_entity_spec.rb70
-rw-r--r--spec/services/bulk_imports/file_decompression_service_spec.rb3
-rw-r--r--spec/services/issues/create_service_spec.rb2
-rw-r--r--spec/support/shared_contexts/sentry_error_tracking_shared_context.rb2
-rw-r--r--spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb67
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb49
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb48
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb2
45 files changed, 1437 insertions, 243 deletions
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index cf0e481495c..475e7d3229c 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -307,6 +307,18 @@ RSpec.describe Projects::ErrorTrackingController do
end
describe 'format json' do
+ context 'when user is a reporter' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it 'returns 404 error' do
+ update_issue
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
context 'when update result is successful' do
before do
allow(issue_update_service).to receive(:execute)
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 2d8454988d9..8070e17b7af 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -70,6 +70,7 @@ RSpec.describe 'Database schema' do
oauth_applications: %w[owner_id],
product_analytics_events_experimental: %w[event_id txn_id user_id],
project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
+ project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id],
project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id],
diff --git a/spec/factories/error_tracking/error.rb b/spec/factories/error_tracking/error.rb
index bebdffb3614..62d16244122 100644
--- a/spec/factories/error_tracking/error.rb
+++ b/spec/factories/error_tracking/error.rb
@@ -13,7 +13,7 @@ FactoryBot.define do
message { 'message' }
culprit { 'culprit' }
external_url { 'http://example.com/id' }
- project_id { 'project1' }
+ project_id { '111111' }
project_name { 'project name' }
project_slug { 'project_name' }
short_id { 'ID' }
diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb
index ed743d8283c..a8ad1af6345 100644
--- a/spec/factories/project_error_tracking_settings.rb
+++ b/spec/factories/project_error_tracking_settings.rb
@@ -9,6 +9,7 @@ FactoryBot.define do
project_name { 'Sentry Project' }
organization_name { 'Sentry Org' }
integrated { false }
+ sentry_project_id { 10 }
trait :disabled do
enabled { false }
diff --git a/spec/finders/ci/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb
index 3569582d70f..755b21ec08f 100644
--- a/spec/finders/ci/runner_jobs_finder_spec.rb
+++ b/spec/finders/ci/runner_jobs_finder_spec.rb
@@ -5,12 +5,17 @@ require 'spec_helper'
RSpec.describe Ci::RunnerJobsFinder do
let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :instance) }
+ let(:user) { create(:user) }
+ let(:params) { {} }
- subject { described_class.new(runner, params).execute }
+ subject { described_class.new(runner, user, params).execute }
+
+ before do
+ project.add_developer(user)
+ end
describe '#execute' do
context 'when params is empty' do
- let(:params) { {} }
let!(:job) { create(:ci_build, runner: runner, project: project) }
let!(:job1) { create(:ci_build, project: project) }
@@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do
end
end
+ context 'when the user has guest access' do
+ it 'does not returns jobs the user does not have permission to see' do
+ another_project = create(:project)
+ job = create(:ci_build, runner: runner, project: another_project)
+
+ another_project.add_guest(user)
+
+ is_expected.not_to match_array(job)
+ end
+ end
+
+ context 'when the user has permission to read all resources' do
+ let(:user) { create(:user, :admin) }
+
+ it 'returns all the jobs assigned to a runner' do
+ jobs = create_list(:ci_build, 5, runner: runner, project: project)
+
+ is_expected.to match_array(jobs)
+ end
+ end
+
+ context 'when the user has different access levels in different projects' do
+ it 'returns only the jobs the user has permission to see' do
+ guest_project = create(:project)
+ reporter_project = create(:project)
+
+ _guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
+ reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
+
+ guest_project.add_guest(user)
+ reporter_project.add_reporter(user)
+
+ is_expected.to match_array(reporter_jobs)
+ end
+ end
+
+ context 'when the user has reporter access level or greater' do
+ it 'returns jobs assigned to the Runner that the user has accesss to' do
+ jobs = create_list(:ci_build, 3, runner: runner, project: project)
+
+ is_expected.to match_array(jobs)
+ end
+ end
+
context 'when params contains status' do
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do
diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb
index b26f8900090..6848786818b 100644
--- a/spec/finders/packages/conan/package_finder_spec.rb
+++ b/spec/finders/packages/conan/package_finder_spec.rb
@@ -2,22 +2,53 @@
require 'spec_helper'
RSpec.describe ::Packages::Conan::PackageFinder do
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :public) }
+ let_it_be(:private_project) { create(:project, :private) }
+
+ let_it_be(:conan_package) { create(:conan_package, project: project) }
+ let_it_be(:conan_package2) { create(:conan_package, project: project) }
+ let_it_be(:errored_package) { create(:conan_package, :error, project: project) }
+ let_it_be(:private_package) { create(:conan_package, project: private_project) }
describe '#execute' do
- let!(:conan_package) { create(:conan_package, project: project) }
- let!(:conan_package2) { create(:conan_package, project: project) }
+ let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
+ let(:finder) { described_class.new(user, query: query) }
+
+ subject { finder.execute }
+
+ where(:visibility, :role, :packages_visible) do
+ :private | :maintainer | true
+ :private | :developer | true
+ :private | :reporter | true
+ :private | :guest | false
+ :private | :anonymous | false
+
+ :internal | :maintainer | true
+ :internal | :developer | true
+ :internal | :reporter | true
+ :internal | :guest | true
+ :internal | :anonymous | false
+
+ :public | :maintainer | true
+ :public | :developer | true
+ :public | :reporter | true
+ :public | :guest | true
+ :public | :anonymous | true
+ end
- subject { described_class.new(user, query: query).execute }
+ with_them do
+ let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] }
+ let(:user) { role == :anonymous ? nil : super() }
- context 'packages that are not installable' do
- let!(:conan_package3) { create(:conan_package, :error, project: project) }
- let!(:non_visible_project) { create(:project, :private) }
- let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) }
- let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
+ before do
+ project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s])
+ project.add_user(user, role) unless role == :anonymous
+ end
- it { is_expected.to eq [conan_package, conan_package2] }
+ it { is_expected.to eq(expected_packages) }
end
end
end
diff --git a/spec/fixtures/api/schemas/entities/member.json b/spec/fixtures/api/schemas/entities/member.json
index dec98123e85..88f7d87b269 100644
--- a/spec/fixtures/api/schemas/entities/member.json
+++ b/spec/fixtures/api/schemas/entities/member.json
@@ -53,7 +53,7 @@
},
"user": {
"allOf": [
- { "$ref": "member_user.json" }
+ { "$ref": "member_user_default.json" }
]
},
"state": { "type": "integer" },
diff --git a/spec/fixtures/api/schemas/entities/member_user_default.json b/spec/fixtures/api/schemas/entities/member_user_default.json
new file mode 100644
index 00000000000..e0b3dba5699
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/member_user_default.json
@@ -0,0 +1,35 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "name",
+ "username",
+ "created_at",
+ "last_activity_on",
+ "avatar_url",
+ "web_url",
+ "blocked",
+ "show_status"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "created_at": { "type": ["string"] },
+ "avatar_url": { "type": ["string", "null"] },
+ "web_url": { "type": "string" },
+ "blocked": { "type": "boolean" },
+ "two_factor_enabled": { "type": "boolean" },
+ "availability": { "type": ["string", "null"] },
+ "last_activity_on": { "type": ["string", "null"] },
+ "status": {
+ "type": "object",
+ "required": ["emoji"],
+ "properties": {
+ "emoji": { "type": "string" }
+ },
+ "additionalProperties": false
+ },
+ "show_status": { "type": "boolean" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/member_user.json b/spec/fixtures/api/schemas/entities/member_user_for_admin_member.json
index 0750e81e115..0750e81e115 100644
--- a/spec/fixtures/api/schemas/entities/member_user.json
+++ b/spec/fixtures/api/schemas/entities/member_user_for_admin_member.json
diff --git a/spec/frontend/notebook/cells/markdown_spec.js b/spec/frontend/notebook/cells/markdown_spec.js
index de415b5bfe0..c757b55faf4 100644
--- a/spec/frontend/notebook/cells/markdown_spec.js
+++ b/spec/frontend/notebook/cells/markdown_spec.js
@@ -130,7 +130,7 @@ describe('Markdown component', () => {
expect(columns[0].innerHTML).toContain('<img src="data:image/jpeg;base64');
expect(columns[1].innerHTML).toContain('<img src="data:image/png;base64');
expect(columns[2].innerHTML).toContain('<img src="data:image/jpeg;base64');
- expect(columns[3].innerHTML).toContain('<img>');
+ expect(columns[3].innerHTML).toContain('<img src="attachment:bogus">');
expect(columns[4].innerHTML).toContain('<img src="https://www.google.com/');
});
});
diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js
index 65b01172e7e..d51360a7597 100644
--- a/spec/frontend/projects/settings/access_dropdown_spec.js
+++ b/spec/frontend/projects/settings/access_dropdown_spec.js
@@ -159,4 +159,21 @@ describe('AccessDropdown', () => {
expect(template).not.toContain(user.name);
});
});
+
+ describe('deployKeyRowHtml', () => {
+ const deployKey = {
+ id: 1,
+ title: 'title <script>alert(document.domain)</script>',
+ fullname: 'fullname <script>alert(document.domain)</script>',
+ avatar_url: '',
+ username: '',
+ };
+
+ it('escapes deploy key title and fullname', () => {
+ const template = dropdown.deployKeyRowHtml(deployKey);
+
+ expect(template).not.toContain(deployKey.title);
+ expect(template).not.toContain(deployKey.fullname);
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
index 74f50b878e2..ee4d3a2630a 100644
--- a/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
+++ b/spec/frontend/vue_shared/components/color_select_dropdown/dropdown_contents_spec.js
@@ -1,57 +1,30 @@
-import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { GlDropdown } from '@gitlab/ui';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import { DROPDOWN_VARIANT } from '~/vue_shared/components/color_select_dropdown/constants';
import DropdownContents from '~/vue_shared/components/color_select_dropdown/dropdown_contents.vue';
import DropdownContentsColorView from '~/vue_shared/components/color_select_dropdown/dropdown_contents_color_view.vue';
+import DropdownHeader from '~/vue_shared/components/color_select_dropdown/dropdown_header.vue';
import { color } from './mock_data';
-const showDropdown = jest.fn();
-const focusInput = jest.fn();
-
const defaultProps = {
dropdownTitle: '',
selectedColor: color,
- dropdownButtonText: '',
+ dropdownButtonText: 'Pick a color',
variant: '',
isVisible: false,
};
-const GlDropdownStub = {
- template: `
- <div>
- <slot name="header"></slot>
- <slot></slot>
- </div>
- `,
- methods: {
- show: showDropdown,
- hide: jest.fn(),
- },
-};
-
-const DropdownHeaderStub = {
- template: `
- <div>Hello, I am a header</div>
- `,
- methods: {
- focusInput,
- },
-};
-
describe('DropdownContent', () => {
let wrapper;
const createComponent = ({ propsData = {} } = {}) => {
- wrapper = shallowMount(DropdownContents, {
+ wrapper = mountExtended(DropdownContents, {
propsData: {
...defaultProps,
...propsData,
},
- stubs: {
- GlDropdown: GlDropdownStub,
- DropdownHeader: DropdownHeaderStub,
- },
});
};
@@ -60,16 +33,17 @@ describe('DropdownContent', () => {
});
const findColorView = () => wrapper.findComponent(DropdownContentsColorView);
- const findDropdownHeader = () => wrapper.findComponent(DropdownHeaderStub);
- const findDropdown = () => wrapper.findComponent(GlDropdownStub);
+ const findDropdownHeader = () => wrapper.findComponent(DropdownHeader);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
it('calls dropdown `show` method on `isVisible` prop change', async () => {
createComponent();
+ const spy = jest.spyOn(wrapper.vm.$refs.dropdown, 'show');
await wrapper.setProps({
isVisible: true,
});
- expect(showDropdown).toHaveBeenCalledTimes(1);
+ expect(spy).toHaveBeenCalledTimes(1);
});
it('does not emit `setColor` event on dropdown hide if color did not change', () => {
@@ -110,4 +84,12 @@ describe('DropdownContent', () => {
expect(findDropdownHeader().exists()).toBe(true);
});
+
+ it('handles no selected color', () => {
+ createComponent({ propsData: { selectedColor: {} } });
+
+ expect(wrapper.findByTestId('fallback-button-text').text()).toEqual(
+ defaultProps.dropdownButtonText,
+ );
+ });
});
diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb
index 7a6104fc503..dc030b1313b 100644
--- a/spec/graphql/resolvers/ci/config_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb
@@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
describe '#resolve' do
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
+ let_it_be(:project) { create(:project, :repository) }
let_it_be(:sha) { nil }
let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
end
- let(:ci_lint) do
- ci_lint_double = instance_double(::Gitlab::Ci::Lint)
- allow(ci_lint_double).to receive(:validate).and_return(fake_result)
-
- ci_lint_double
- end
-
- before do
- allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
- end
-
subject(:response) do
resolve(described_class,
args: { project_path: project.full_path, content: content, sha: sha },
@@ -51,52 +40,77 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end
end
- context 'with a valid .gitlab-ci.yml' do
- context 'with a sha' do
- let(:sha) { '1231231' }
+ context 'when the user can create a pipeline' do
+ let(:ci_lint) do
+ ci_lint_double = instance_double(::Gitlab::Ci::Lint)
+ allow(ci_lint_double).to receive(:validate).and_return(fake_result)
- it_behaves_like 'a valid config file'
+ ci_lint_double
end
- context 'without a sha' do
- it_behaves_like 'a valid config file'
+ before do
+ allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
+
+ project.add_developer(user)
end
- end
- context 'with an invalid .gitlab-ci.yml' do
- let(:content) { 'invalid' }
+ context 'with a valid .gitlab-ci.yml' do
+ context 'with a sha' do
+ let(:sha) { '1231231' }
- let(:fake_result) do
- Gitlab::Ci::Lint::Result.new(
- jobs: [],
- merged_yaml: content,
- errors: ['Invalid configuration format'],
- warnings: [],
- includes: []
- )
+ it_behaves_like 'a valid config file'
+ end
+
+ context 'without a sha' do
+ it_behaves_like 'a valid config file'
+ end
end
- it 'responds with errors about invalid syntax' do
- expect(response[:status]).to eq(:invalid)
- expect(response[:errors]).to eq(['Invalid configuration format'])
+ context 'with an invalid .gitlab-ci.yml' do
+ let(:content) { 'invalid' }
+
+ let(:fake_result) do
+ Gitlab::Ci::Lint::Result.new(
+ jobs: [],
+ merged_yaml: content,
+ errors: ['Invalid configuration format'],
+ warnings: [],
+ includes: []
+ )
+ end
+
+ it 'responds with errors about invalid syntax' do
+ expect(response[:status]).to eq(:invalid)
+ expect(response[:errors]).to match_array(['Invalid configuration format'])
+ end
end
- end
- context 'with an invalid SHA' do
- let_it_be(:sha) { ':' }
+ context 'with an invalid SHA' do
+ let_it_be(:sha) { ':' }
- let(:ci_lint) do
- ci_lint_double = instance_double(::Gitlab::Ci::Lint)
- allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
+ let(:ci_lint) do
+ ci_lint_double = instance_double(::Gitlab::Ci::Lint)
+ allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
- ci_lint_double
+ ci_lint_double
+ end
+
+ it 'logs the invalid SHA to Sentry' do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
+ .with(GRPC::InvalidArgument, sha: ':')
+
+ response
+ end
end
+ end
- it 'logs the invalid SHA to Sentry' do
- expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
- .with(GRPC::InvalidArgument, sha: ':')
+ context 'when the user cannot create a pipeline' do
+ before do
+ project.add_guest(user)
+ end
- response
+ it 'returns an error stating that the user cannot access the linting' do
+ expect(response).to be_instance_of(::Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb
index dccbc110be6..95dfc51e8fd 100644
--- a/spec/helpers/integrations_helper_spec.rb
+++ b/spec/helpers/integrations_helper_spec.rb
@@ -150,19 +150,4 @@ RSpec.describe IntegrationsHelper do
end
end
end
-
- describe '#jira_issue_breadcrumb_link' do
- let(:issue_reference) { nil }
-
- subject { helper.jira_issue_breadcrumb_link(issue_reference) }
-
- context 'when issue_reference contains HTML' do
- let(:issue_reference) { "<script>alert('XSS')</script>" }
-
- it 'escapes issue reference' do
- is_expected.not_to include(issue_reference)
- is_expected.to include(html_escape(issue_reference))
- end
- end
- end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index e0c98bbc161..4502729866c 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do
context 'api_url present' do
let(:json) do
{
+ sentry_project_id: error_tracking_setting.sentry_project_id,
name: error_tracking_setting.project_name,
organization_name: error_tracking_setting.organization_name,
organization_slug: error_tracking_setting.organization_slug,
diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb
index e31f2df7372..f9fb40a616b 100644
--- a/spec/helpers/timeboxes_helper_spec.rb
+++ b/spec/helpers/timeboxes_helper_spec.rb
@@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do
end
end
end
+
+ describe "#recent_releases_with_counts" do
+ let_it_be(:milestone) { create(:milestone) }
+ let_it_be(:project) { milestone.project }
+ let_it_be(:user) { create(:user) }
+
+ subject { helper.recent_releases_with_counts(milestone, user) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it "returns releases with counts" do
+ _old_releases = create_list(:release, 2, project: project, milestones: [milestone])
+ recent_public_releases = create_list(:release, 3, project: project, milestones: [milestone], released_at: '2022-01-01T18:00:00Z')
+
+ is_expected.to match([match_array(recent_public_releases), 5, 2])
+ end
+ end
end
diff --git a/spec/initializers/net_http_response_patch_spec.rb b/spec/initializers/net_http_response_patch_spec.rb
new file mode 100644
index 00000000000..3bd0d8c3907
--- /dev/null
+++ b/spec/initializers/net_http_response_patch_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Net::HTTPResponse patch header read timeout' do
+ describe '.each_response_header' do
+ let(:server_response) do
+ <<~EOS
+ Content-Type: text/html
+ Header-Two: foo
+
+ Hello World
+ EOS
+ end
+
+ before do
+ stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
+ end
+
+ subject(:each_response_header) { Net::HTTPResponse.each_response_header(socket) { |k, v| } }
+
+ context 'with Net::BufferedIO' do
+ let(:socket) { Net::BufferedIO.new(StringIO.new(server_response)) }
+
+ it 'does not forward start time to the socket' do
+ allow(socket).to receive(:readuntil).and_call_original
+ expect(socket).to receive(:readuntil).with("\n", true)
+
+ each_response_header
+ end
+
+ context 'when the response contains many consecutive spaces' do
+ before do
+ expect(socket).to receive(:readuntil).and_return(
+ "a: #{' ' * 100_000} b",
+ ''
+ )
+ end
+
+ it 'has no regex backtracking issues' do
+ Timeout.timeout(1) do
+ each_response_header
+ end
+ end
+ end
+ end
+
+ context 'with Gitlab::BufferedIo' do
+ let(:mock_io) { StringIO.new(server_response) }
+ let(:socket) { Gitlab::BufferedIo.new(mock_io) }
+
+ it 'forwards start time to the socket' do
+ allow(socket).to receive(:readuntil).and_call_original
+ expect(socket).to receive(:readuntil).with("\n", true, kind_of(Numeric))
+
+ each_response_header
+ end
+
+ context 'when the response contains an infinite number of headers' do
+ before do
+ read_counter = 0
+
+ allow(mock_io).to receive(:read_nonblock) do
+ read_counter += 1
+ raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
+
+ sleep 0.01
+ +"Yet-Another-Header: foo\n"
+ end
+ end
+
+ it 'raises a timeout error' do
+ expect { each_response_header }.to raise_error(Gitlab::HTTP::HeaderReadTimeout,
+ /Request timed out after reading headers for 0\.[0-9]+ seconds/)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb
index c53c0849931..567a0a4fcc3 100644
--- a/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb
@@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
let(:project_data) do
{
'visibility' => 'private',
- 'created_at' => 10.days.ago,
- 'archived' => false,
- 'shared_runners_enabled' => true,
- 'container_registry_enabled' => true,
- 'only_allow_merge_if_pipeline_succeeds' => true,
- 'only_allow_merge_if_all_discussions_are_resolved' => true,
- 'request_access_enabled' => true,
- 'printing_merge_request_link_enabled' => true,
- 'remove_source_branch_after_merge' => true,
- 'autoclose_referenced_issues' => true,
- 'suggestion_commit_message' => 'message',
- 'wiki_enabled' => true
+ 'created_at' => '2016-08-12T09:41:03'
}
end
@@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
expect(imported_project).not_to be_nil
expect(imported_project.group).to eq(group)
- expect(imported_project.suggestion_commit_message).to eq('message')
- expect(imported_project.archived?).to eq(project_data['archived'])
- expect(imported_project.shared_runners_enabled?).to eq(project_data['shared_runners_enabled'])
- expect(imported_project.container_registry_enabled?).to eq(project_data['container_registry_enabled'])
- expect(imported_project.only_allow_merge_if_pipeline_succeeds?).to eq(project_data['only_allow_merge_if_pipeline_succeeds'])
- expect(imported_project.only_allow_merge_if_all_discussions_are_resolved?).to eq(project_data['only_allow_merge_if_all_discussions_are_resolved'])
- expect(imported_project.request_access_enabled?).to eq(project_data['request_access_enabled'])
- expect(imported_project.printing_merge_request_link_enabled?).to eq(project_data['printing_merge_request_link_enabled'])
- expect(imported_project.remove_source_branch_after_merge?).to eq(project_data['remove_source_branch_after_merge'])
- expect(imported_project.autoclose_referenced_issues?).to eq(project_data['autoclose_referenced_issues'])
- expect(imported_project.wiki_enabled?).to eq(project_data['wiki_enabled'])
+ expect(imported_project.visibility).to eq(project_data['visibility'])
+ expect(imported_project.created_at).to eq(project_data['created_at'])
end
end
diff --git a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb
index 822bb9a5605..a1d77b9732d 100644
--- a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb
+++ b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb
@@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
let(:data) do
{
- 'name' => 'source_name',
- 'visibility' => 'private'
+ 'visibility' => 'private',
+ 'created_at' => '2016-11-18T09:29:42.634Z'
}
end
@@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
end
end
- it 'converts all keys to symbols' do
- expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id)
+ context 'when data has extra keys' do
+ it 'returns a fixed number of keys' do
+ data = {
+ 'visibility' => 'private',
+ 'created_at' => '2016-11-18T09:29:42.634Z',
+ 'my_key' => 'my_key',
+ 'another_key' => 'another_key',
+ 'last_key' => 'last_key'
+ }
+
+ transformed_data = described_class.new.transform(context, data)
+
+ expect(transformed_data.keys)
+ .to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level)
+ end
end
end
end
diff --git a/spec/lib/gitlab/buffered_io_spec.rb b/spec/lib/gitlab/buffered_io_spec.rb
index f8896abd46e..c6939b819e2 100644
--- a/spec/lib/gitlab/buffered_io_spec.rb
+++ b/spec/lib/gitlab/buffered_io_spec.rb
@@ -1,54 +1,50 @@
-# rubocop:disable Style/FrozenStringLiteralComment
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe Gitlab::BufferedIo do
describe '#readuntil' do
- let(:never_ending_tcp_socket) do
- Class.new do
- def initialize(*_)
- @read_counter = 0
- end
+ let(:mock_io) { StringIO.new('a') }
+ let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
- def setsockopt(*_); end
+ before do
+ stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
+ end
- def closed?
- false
- end
+ subject(:readuntil) do
+ Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time)
+ end
- def close
- true
- end
+ it 'does not raise a timeout error' do
+ expect { readuntil }.not_to raise_error
+ end
- def to_io
- StringIO.new('Hello World!')
- end
+ context 'when the response contains infinitely long headers' do
+ before do
+ read_counter = 0
- def write_nonblock(data, *_)
- data.size
- end
+ allow(mock_io).to receive(:read_nonblock) do |buffer_size, *_|
+ read_counter += 1
+ raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
- def read_nonblock(buffer_size, *_)
sleep 0.01
- @read_counter += 1
-
- raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10
-
'H' * buffer_size
end
end
- end
- before do
- stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
- end
+ it 'raises a timeout error' do
+ expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
+ end
- subject(:readuntil) do
- Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a')
- end
+ context 'when not passing start_time' do
+ subject(:readuntil) do
+ Gitlab::BufferedIo.new(mock_io).readuntil('a', false)
+ end
- it 'raises a timeout error' do
- expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
+ it 'raises a timeout error' do
+ expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
+ end
+ end
end
end
end
-# rubocop:enable Style/FrozenStringLiteralComment
diff --git a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
index 3953a1aa81a..fd3218d7d84 100644
--- a/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
+++ b/spec/lib/gitlab/ci/runner_upgrade_check_spec.rb
@@ -21,7 +21,9 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
end
context 'with available_runner_releases configured up to 14.1.1' do
- let(:available_runner_releases) { %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2 14.1.0 14.1.1] }
+ let(:available_runner_releases) do
+ %w[13.9.0 13.9.1 13.9.2 13.10.0 13.10.1 14.0.0 14.0.1 14.0.2-rc1 14.0.2 14.1.0 14.1.1]
+ end
context 'with nil runner_version' do
let(:runner_version) { nil }
@@ -62,10 +64,11 @@ RSpec.describe Gitlab::Ci::RunnerUpgradeCheck do
'v14.1.0/1.1.0' | :recommended # suffixes are correctly handled
'v14.1.0' | :recommended # recommended since even though the GitLab instance is still on 14.0.x, there is a patch release (14.1.1) available which might contain security fixes
'v14.0.1' | :recommended # recommended upgrade since 14.0.2 is available
+ 'v14.0.2-rc1' | :recommended # recommended upgrade since 14.0.2 is available and we'll move out of a release candidate
'v14.0.2' | :not_available # not available since 14.0.2 is the latest 14.0.x release available within the instance's major.minor version
'v13.10.1' | :available # available upgrade: 14.1.1
- 'v13.10.1~beta.1574.gf6ea9389' | :available # suffixes are correctly handled
- 'v13.10.1/1.1.0' | :available # suffixes are correctly handled
+ 'v13.10.1~beta.1574.gf6ea9389' | :recommended # suffixes are correctly handled, official 13.10.1 is available
+ 'v13.10.1/1.1.0' | :recommended # suffixes are correctly handled, official 13.10.1 is available
'v13.10.0' | :recommended # recommended upgrade since 13.10.1 is available
'v13.9.2' | :recommended # recommended upgrade since backports are no longer released for this version
'v13.9.0' | :recommended # recommended upgrade since backports are no longer released for this version
diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
new file mode 100644
index 00000000000..116917d3e06
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
+ subject(:importer) { described_class.new(project, user.id) }
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:user) { create(:user) }
+
+ let(:issue) { create(:issue, project: project) }
+ let(:commit_id) { nil }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => 'closed',
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'commit_id' => commit_id,
+ 'issue_db_id' => issue.id
+ )
+ end
+
+ let(:expected_event_attrs) do
+ {
+ project_id: project.id,
+ author_id: user.id,
+ target_id: issue.id,
+ target_type: Issue.name,
+ action: 'closed',
+ created_at: issue_event.created_at,
+ updated_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ let(:expected_state_event_attrs) do
+ {
+ user_id: user.id,
+ issue_id: issue.id,
+ state: 'closed',
+ created_at: issue_event.created_at
+ }.stringify_keys
+ end
+
+ it 'creates expected event and state event' do
+ importer.execute(issue_event)
+
+ expect(issue.events.count).to eq 1
+ expect(issue.events[0].attributes)
+ .to include expected_event_attrs
+
+ expect(issue.resource_state_events.count).to eq 1
+ expect(issue.resource_state_events[0].attributes)
+ .to include expected_state_event_attrs
+ end
+
+ context 'when closed by commit' do
+ let!(:closing_commit) { create(:commit, project: project) }
+ let(:commit_id) { closing_commit.id }
+
+ it 'creates expected event and state event' do
+ importer.execute(issue_event)
+
+ expect(issue.events.count).to eq 1
+ state_event = issue.resource_state_events.last
+ expect(state_event.source_commit).to eq commit_id[0..40]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
new file mode 100644
index 00000000000..03a80c11f96
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab_redis_cache do
+ let(:importer) { described_class.new(issue_event, project, client) }
+
+ let(:project) { create(:project) }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+
+ let(:issue_event) do
+ Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => actor_id, 'login' => 'alice' },
+ 'event' => event_name,
+ 'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'commit_url' =>
+ 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'performed_via_github_app' => nil
+ )
+ end
+
+ let(:actor_id) { user.id }
+ let(:event_name) { 'closed' }
+
+ shared_examples 'triggers specific event importer' do |importer_class|
+ it importer_class.name do
+ specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
+
+ expect(importer_class)
+ .to receive(:new).with(project, user.id)
+ .and_return(specific_importer)
+ expect(specific_importer).to receive(:execute).with(issue_event)
+
+ importer.execute
+ end
+ end
+
+ describe '#execute' do
+ before do
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:author_id_for)
+ .with(issue_event, author_key: :actor)
+ .and_return(user.id, true)
+ end
+
+ issue_event.attributes[:issue_db_id] = issue.id
+ end
+
+ context "when it's closed issue event" do
+ let(:event_name) { 'closed' }
+
+ it_behaves_like 'triggers specific event importer',
+ Gitlab::GithubImport::Importer::Events::Closed
+ end
+
+ context "when it's unknown issue event" do
+ let(:event_name) { 'fake' }
+
+ it 'logs warning and skips' do
+ expect(Gitlab::GithubImport::Logger).to receive(:debug)
+ .with(
+ message: 'UNSUPPORTED_EVENT_TYPE',
+ event_type: issue_event.event,
+ event_github_id: issue_event.id
+ )
+
+ importer.execute
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
new file mode 100644
index 00000000000..087faeffe02
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter do
+ let(:client) { double }
+
+ let_it_be(:project) { create(:project, :import_started, import_source: 'http://somegithub.com') }
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ subject { described_class.new(project, client, parallel: parallel) }
+
+ let(:parallel) { true }
+
+ it { is_expected.to include_module(Gitlab::GithubImport::ParallelScheduling) }
+
+ describe '#importer_class' do
+ it { expect(subject.importer_class).to eq(Gitlab::GithubImport::Importer::IssueEventImporter) }
+ end
+
+ describe '#representation_class' do
+ it { expect(subject.representation_class).to eq(Gitlab::GithubImport::Representation::IssueEvent) }
+ end
+
+ describe '#sidekiq_worker_class' do
+ it { expect(subject.sidekiq_worker_class).to eq(Gitlab::GithubImport::ImportIssueEventWorker) }
+ end
+
+ describe '#object_type' do
+ it { expect(subject.object_type).to eq(:issue_event) }
+ end
+
+ describe '#collection_method' do
+ it { expect(subject.collection_method).to eq(:issue_timeline) }
+ end
+
+ describe '#page_counter_id' do
+ it { expect(subject.page_counter_id(issue)).to eq("issues/#{issue.iid}/issue_timeline") }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ let(:event) { instance_double('Event', id: 1) }
+
+ it { expect(subject.id_for_already_imported_cache(event)).to eq(1) }
+ end
+
+ describe '#collection_options' do
+ it do
+ expect(subject.collection_options)
+ .to eq({ state: 'all', sort: 'created', direction: 'asc' })
+ end
+ end
+
+ describe '#each_object_to_import', :clean_gitlab_redis_cache do
+ let(:issue_event) do
+ struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
+ struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
+ end
+
+ let(:page) do
+ instance_double(
+ Gitlab::GithubImport::Client::Page,
+ number: 1, objects: [issue_event]
+ )
+ end
+
+ let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) }
+
+ before do
+ allow(client).to receive(:each_page)
+ .once
+ .with(
+ :issue_timeline,
+ project.import_source,
+ issue.iid,
+ { state: 'all', sort: 'created', direction: 'asc', page: 1 }
+ ).and_yield(page)
+ end
+
+ it 'imports each issue event page by page' do
+ counter = 0
+ subject.each_object_to_import do |object|
+ expect(object).to eq issue_event
+ expect(issue_event.issue_db_id).to eq issue.id
+ counter += 1
+ end
+ expect(counter).to eq 1
+ end
+
+ it 'triggers page number increment' do
+ expect(Gitlab::GithubImport::PageCounter)
+ .to receive(:new).with(project, 'issues/1/issue_timeline')
+ .and_return(page_counter)
+ expect(page_counter).to receive(:current).and_return(1)
+ expect(page_counter)
+ .to receive(:set).with(page.number).and_return(true)
+
+ counter = 0
+ subject.each_object_to_import { counter += 1 }
+ expect(counter).to eq 1
+ end
+
+ context 'when page is already processed' do
+ before do
+ page_counter = Gitlab::GithubImport::PageCounter.new(
+ project, subject.page_counter_id(issue)
+ )
+ page_counter.set(page.number)
+ end
+
+ it "doesn't process this page" do
+ counter = 0
+ subject.each_object_to_import { counter += 1 }
+ expect(counter).to eq 0
+ end
+ end
+
+ context 'when event is already processed' do
+ it "doesn't process this event" do
+ subject.mark_as_imported(issue_event)
+
+ counter = 0
+ subject.each_object_to_import { counter += 1 }
+ expect(counter).to eq 0
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
new file mode 100644
index 00000000000..412e1aefc19
--- /dev/null
+++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
+ shared_examples 'an IssueEvent' do
+ it 'returns an instance of IssueEvent' do
+ expect(issue_event).to be_an_instance_of(described_class)
+ end
+
+ context 'the returned IssueEvent' do
+ it 'includes the issue event id' do
+ expect(issue_event.id).to eq(6501124486)
+ end
+
+ it 'includes the issue event "event"' do
+ expect(issue_event.event).to eq('closed')
+ end
+
+ it 'includes the issue event commit_id' do
+ expect(issue_event.commit_id).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ end
+
+ it 'includes the issue_db_id' do
+ expect(issue_event.issue_db_id).to eq(100500)
+ end
+
+ context 'when actor data present' do
+ it 'includes the actor details' do
+ expect(issue_event.actor)
+ .to be_an_instance_of(Gitlab::GithubImport::Representation::User)
+
+ expect(issue_event.actor.id).to eq(4)
+ expect(issue_event.actor.login).to eq('alice')
+ end
+ end
+
+ context 'when actor data is empty' do
+ let(:with_actor) { false }
+
+ it 'does not return such info' do
+ expect(issue_event.actor).to eq nil
+ end
+ end
+
+ it 'includes the created timestamp' do
+ expect(issue_event.created_at).to eq('2022-04-26 18:30:53 UTC')
+ end
+ end
+
+ describe '#github_identifiers' do
+ it 'returns a hash with needed identifiers' do
+ expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
+ end
+ end
+ end
+
+ describe '.from_api_response' do
+ let(:response) do
+ event_resource = Struct.new(
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url,
+ :issue_db_id, :created_at, :performed_via_github_app,
+ keyword_init: true
+ )
+ user_resource = Struct.new(:id, :login, keyword_init: true)
+ event_resource.new(
+ id: 6501124486,
+ node_id: 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ url: 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ actor: with_actor ? user_resource.new(id: 4, login: 'alice') : nil,
+ event: 'closed',
+ commit_id: '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ commit_url: 'https://api.github.com/repos/octocat/Hello-World/commits'\
+ '/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ issue_db_id: 100500,
+ created_at: '2022-04-26 18:30:53 UTC',
+ performed_via_github_app: nil
+ )
+ end
+
+ let(:with_actor) { true }
+
+ it_behaves_like 'an IssueEvent' do
+ let(:issue_event) { described_class.from_api_response(response) }
+ end
+ end
+
+ describe '.from_json_hash' do
+ it_behaves_like 'an IssueEvent' do
+ let(:hash) do
+ {
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => (with_actor ? { 'id' => 4, 'login' => 'alice' } : nil),
+ 'event' => 'closed',
+ 'commit_id' => '570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ 'commit_url' =>
+ 'https://api.github.com/repos/octocat/Hello-World/commits/570e7b2abdd848b95f2f578043fc23bd6f6fd24d',
+ "issue_db_id" => 100500,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'performed_via_github_app' => nil
+ }
+ end
+
+ let(:with_actor) { true }
+
+ let(:issue_event) { described_class.from_json_hash(hash) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb b/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb
new file mode 100644
index 00000000000..64dbc939348
--- /dev/null
+++ b/spec/lib/gitlab/github_import/single_endpoint_notes_importing_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::SingleEndpointNotesImporting do
+ let(:importer_class) do
+ Class.new do
+ def self.name
+ 'MyImporter'
+ end
+
+ include(Gitlab::GithubImport::SingleEndpointNotesImporting)
+ end
+ end
+
+ let(:importer_instance) { importer_class.new }
+
+ describe '#parent_collection' do
+ it { expect { importer_instance.parent_collection }.to raise_error(NotImplementedError) }
+ end
+
+ describe '#parent_imported_cache_key' do
+ it { expect { importer_instance.parent_imported_cache_key }.to raise_error(NotImplementedError) }
+ end
+
+ describe '#page_counter_id' do
+ it { expect { importer_instance.page_counter_id(build(:merge_request)) }.to raise_error(NotImplementedError) }
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 6a49e47e761..af910b08fae 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -420,6 +420,8 @@ project:
- zentao_integration
# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/417
- dingtalk_integration
+# dingtalk_integration JiHu-specific, see https://jihulab.com/gitlab-cn/gitlab/-/merge_requests/640
+- feishu_integration
- redmine_integration
- youtrack_integration
- custom_issue_tracker_integration
diff --git a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
index fe3b638d20f..dea584e5019 100644
--- a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
+++ b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb
@@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
include_examples 'logs raised exception and terminates validator process group'
end
end
+
+ context 'archive path validation' do
+ let(:filesize) { nil }
+
+ before do
+ expect(Gitlab::Import::Logger)
+ .to receive(:info)
+ .with(
+ import_upload_archive_path: filepath,
+ import_upload_archive_size: filesize,
+ message: error_message
+ )
+ end
+
+ context 'when archive path is traversed' do
+ let(:filepath) { '/foo/../bar' }
+ let(:error_message) { 'Invalid path' }
+
+ it 'returns false' do
+ expect(subject.valid?).to eq(false)
+ end
+ end
+
+ context 'when archive path is not a string' do
+ let(:filepath) { 123 }
+ let(:error_message) { 'Archive path is not a string' }
+
+ it 'returns false' do
+ expect(subject.valid?).to eq(false)
+ end
+ end
+
+ context 'which archive path is a symlink' do
+ let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
+ let(:error_message) { 'Archive path is a symlink' }
+
+ before do
+ FileUtils.ln_s(filepath, filepath, force: true)
+ end
+
+ it 'returns false' do
+ expect(subject.valid?).to eq(false)
+ end
+ end
+
+ context 'when archive path is not a file' do
+ let(:filepath) { Dir.mktmpdir }
+ let(:filesize) { File.size(filepath) }
+ let(:error_message) { 'Archive path is not a file' }
+
+ after do
+ FileUtils.rm_rf(filepath)
+ end
+
+ it 'returns false' do
+ expect(subject.valid?).to eq(false)
+ end
+ end
+ end
end
def create_compressed_file
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index a77d51fab88..6ed094f11c8 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -2,28 +2,44 @@
require 'fast_spec_helper'
-RSpec.describe 'Gitlab::VersionInfo' do
+RSpec.describe Gitlab::VersionInfo do
before do
- @unknown = Gitlab::VersionInfo.new
- @v0_0_1 = Gitlab::VersionInfo.new(0, 0, 1)
- @v0_1_0 = Gitlab::VersionInfo.new(0, 1, 0)
- @v1_0_0 = Gitlab::VersionInfo.new(1, 0, 0)
- @v1_0_1 = Gitlab::VersionInfo.new(1, 0, 1)
- @v1_1_0 = Gitlab::VersionInfo.new(1, 1, 0)
- @v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0)
+ @unknown = described_class.new
+ @v0_0_1 = described_class.new(0, 0, 1)
+ @v0_1_0 = described_class.new(0, 1, 0)
+ @v1_0_0 = described_class.new(1, 0, 0)
+ @v1_0_1 = described_class.new(1, 0, 1)
+ @v1_0_1_b1 = described_class.new(1, 0, 1, '-b1')
+ @v1_0_1_rc1 = described_class.new(1, 0, 1, '-rc1')
+ @v1_0_1_rc2 = described_class.new(1, 0, 1, '-rc2')
+ @v1_1_0 = described_class.new(1, 1, 0)
+ @v1_1_0_beta1 = described_class.new(1, 1, 0, '-beta1')
+ @v2_0_0 = described_class.new(2, 0, 0)
+ @v13_10_1_1574_89 = described_class.parse("v13.10.1~beta.1574.gf6ea9389", parse_suffix: true)
+ @v13_10_1_1575_89 = described_class.parse("v13.10.1~beta.1575.gf6ea9389", parse_suffix: true)
+ @v13_10_1_1575_90 = described_class.parse("v13.10.1~beta.1575.gf6ea9390", parse_suffix: true)
end
describe '>' do
it { expect(@v2_0_0).to be > @v1_1_0 }
it { expect(@v1_1_0).to be > @v1_0_1 }
+ it { expect(@v1_0_1_b1).to be > @v1_0_0 }
+ it { expect(@v1_0_1_rc1).to be > @v1_0_0 }
+ it { expect(@v1_0_1_rc1).to be > @v1_0_1_b1 }
+ it { expect(@v1_0_1_rc2).to be > @v1_0_1_rc1 }
+ it { expect(@v1_0_1).to be > @v1_0_1_rc1 }
+ it { expect(@v1_0_1).to be > @v1_0_1_rc2 }
it { expect(@v1_0_1).to be > @v1_0_0 }
it { expect(@v1_0_0).to be > @v0_1_0 }
+ it { expect(@v1_1_0_beta1).to be > @v1_0_1_rc2 }
+ it { expect(@v1_1_0).to be > @v1_1_0_beta1 }
it { expect(@v0_1_0).to be > @v0_0_1 }
end
describe '>=' do
- it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) }
+ it { expect(@v2_0_0).to be >= described_class.new(2, 0, 0) }
it { expect(@v2_0_0).to be >= @v1_1_0 }
+ it { expect(@v1_0_1_rc2).to be >= @v1_0_1_rc1 }
end
describe '<' do
@@ -31,64 +47,115 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0).to be < @v1_0_0 }
it { expect(@v1_0_0).to be < @v1_0_1 }
it { expect(@v1_0_1).to be < @v1_1_0 }
+ it { expect(@v1_0_0).to be < @v1_0_1_rc2 }
+ it { expect(@v1_0_1_rc1).to be < @v1_0_1 }
+ it { expect(@v1_0_1_rc1).to be < @v1_0_1_rc2 }
+ it { expect(@v1_0_1_rc2).to be < @v1_0_1 }
it { expect(@v1_1_0).to be < @v2_0_0 }
+ it { expect(@v13_10_1_1574_89).to be < @v13_10_1_1575_89 }
+ it { expect(@v13_10_1_1575_89).to be < @v13_10_1_1575_90 }
end
describe '<=' do
- it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) }
+ it { expect(@v0_0_1).to be <= described_class.new(0, 0, 1) }
it { expect(@v0_0_1).to be <= @v0_1_0 }
+ it { expect(@v1_0_1_b1).to be <= @v1_0_1_rc1 }
+ it { expect(@v1_0_1_rc1).to be <= @v1_0_1_rc2 }
+ it { expect(@v1_1_0_beta1).to be <= @v1_1_0 }
end
describe '==' do
- it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) }
- it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) }
- it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) }
+ it { expect(@v0_0_1).to eq(described_class.new(0, 0, 1)) }
+ it { expect(@v0_1_0).to eq(described_class.new(0, 1, 0)) }
+ it { expect(@v1_0_0).to eq(described_class.new(1, 0, 0)) }
+ it { expect(@v1_0_1_rc1).to eq(described_class.new(1, 0, 1, '-rc1')) }
end
describe '!=' do
it { expect(@v0_0_1).not_to eq(@v0_1_0) }
+ it { expect(@v1_0_1_rc1).not_to eq(@v1_0_1_rc2) }
end
describe '.unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
- it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
+ it { expect(@unknown).not_to be described_class.new }
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
describe '.parse' do
- it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) }
- it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
+ it { expect(described_class.parse("1.0.0")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0.1")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0-ee")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0-rc1")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("git 1.0.0b1")).to eq(@v1_0_0) }
+ it { expect(described_class.parse("git 1.0b1")).not_to be_valid }
+
+ context 'with parse_suffix: true' do
+ let(:versions) do
+ <<-VERSIONS.lines
+ 0.0.1
+ 0.1.0
+ 1.0.0
+ 1.0.1-b1
+ 1.0.1-rc1
+ 1.0.1-rc2
+ 1.0.1
+ 1.1.0-beta1
+ 1.1.0
+ 2.0.0
+ v13.10.0-pre
+ v13.10.0-rc1
+ v13.10.0-rc2
+ v13.10.0
+ v13.10.1~beta.1574.gf6ea9389
+ v13.10.1~beta.1575.gf6ea9389
+ v13.10.1-rc1
+ v13.10.1-rc2
+ v13.10.1
+ VERSIONS
+ end
+
+ let(:parsed_versions) do
+ versions.map(&:strip).map { |version| described_class.parse(version, parse_suffix: true) }
+ end
+
+ it 'versions are returned in a correct order' do
+ expect(parsed_versions.shuffle.sort).to eq(parsed_versions)
+ end
+ end
end
describe '.to_s' do
it { expect(@v1_0_0.to_s).to eq("1.0.0") }
+ it { expect(@v1_0_1_rc1.to_s).to eq("1.0.1-rc1") }
it { expect(@unknown.to_s).to eq("Unknown") }
end
describe '.hash' do
- it { expect(Gitlab::VersionInfo.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
- it { expect(Gitlab::VersionInfo.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
- it { expect(Gitlab::VersionInfo.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
+ it { expect(described_class.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
+ it { expect(described_class.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
+ it { expect(described_class.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
+ it { expect(described_class.parse("1.0.1-rc1", parse_suffix: true).hash).to eq(@v1_0_1_rc1.hash) }
end
describe '.eql?' do
- it { expect(Gitlab::VersionInfo.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
- it { expect(Gitlab::VersionInfo.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
+ it { expect(described_class.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
+ it { expect(described_class.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc1)).to be_truthy }
+ it { expect(@v1_0_1_rc1.eql?(@v1_0_1_rc2)).to be_falsey }
+ it { expect(@v1_0_1_rc1.eql?(@v1_0_1)).to be_falsey }
it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
- it { expect([@v1_0_0, @v1_1_0, @v1_0_0].uniq).to eq [@v1_0_0, @v1_1_0] }
+ it { expect([@v1_0_0, @v1_1_0, @v1_0_0, @v1_0_1_rc1, @v1_0_1_rc1].uniq).to eq [@v1_0_0, @v1_1_0, @v1_0_1_rc1] }
end
describe '.same_minor_version?' do
it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_1_rc1.same_minor_version?(@v1_0_0)).to be_truthy }
it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
@@ -98,5 +165,6 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
+ it { expect(@v1_0_1_rc1.without_patch).to eq(@v1_0_0) }
end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 61f008416ea..dcb9890c7cd 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1065,6 +1065,14 @@ RSpec.describe ApplicationSetting do
it { is_expected.to validate_numericality_of(:sidekiq_job_limiter_compression_threshold_bytes).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:sidekiq_job_limiter_limit_bytes).only_integer.is_greater_than_or_equal_to(0) }
end
+
+ context 'prometheus settings' do
+ it 'validates metrics_method_call_threshold' do
+ allow(subject).to receive(:prometheus_metrics_enabled).and_return(true)
+
+ is_expected.to validate_numericality_of(:metrics_method_call_threshold).is_greater_than_or_equal_to(0)
+ end
+ end
end
context 'restrict creating duplicates' do
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index 2939a40a84f..15b6b45eaba 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
let(:sentry_client) { instance_double(ErrorTracking::SentryClient) }
- subject(:setting) { build(:project_error_tracking_setting, project: project) }
+ let(:sentry_project_id) { 10 }
+
+ subject(:setting) { build(:project_error_tracking_setting, project: project, sentry_project_id: sentry_project_id) }
describe 'Associations' do
it { is_expected.to belong_to(:project) }
@@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end
describe '#issue_details' do
- let(:issue) { build(:error_tracking_sentry_detailed_error) }
+ let(:issue) { build(:error_tracking_sentry_detailed_error, project_id: sentry_project_id) }
let(:commit_id) { issue.first_release_version }
let(:result) { subject.issue_details(opts) }
let(:opts) { { issue_id: 1 } }
@@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end
end
+ describe '#issue_latest_event' do
+ let(:error_event) { build(:error_tracking_sentry_error_event, project_id: sentry_project_id) }
+ let(:result) { subject.issue_latest_event(opts) }
+ let(:opts) { { issue_id: 1 } }
+
+ before do
+ stub_reactive_cache(subject, error_event, {})
+ synchronous_reactive_cache(subject)
+
+ allow(subject).to receive(:sentry_client).and_return(sentry_client)
+ allow(sentry_client).to receive(:issue_latest_event).with(opts).and_return(error_event)
+ end
+
+ it 'returns the error event' do
+ expect(result[:latest_event].project_id).to eq(sentry_project_id)
+ end
+ end
+
describe '#update_issue' do
let(:result) { subject.update_issue(**opts) }
let(:opts) { { issue_id: 1, params: {} } }
before do
allow(subject).to receive(:sentry_client).and_return(sentry_client)
+ allow(sentry_client).to receive(:issue_details)
+ .with({ issue_id: 1 })
+ .and_return(Gitlab::ErrorTracking::DetailedError.new(project_id: sentry_project_id))
end
context 'when sentry response is successful' do
@@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
expect(result).to eq(error: 'Unexpected Error')
end
end
+
+ context 'when sentry_project_id is not set' do
+ let(:sentry_projects) do
+ [
+ Gitlab::ErrorTracking::Project.new(
+ id: 1111,
+ name: 'Some Project',
+ organization_name: 'Org'
+ ),
+ Gitlab::ErrorTracking::Project.new(
+ id: sentry_project_id,
+ name: setting.project_name,
+ organization_name: setting.organization_name
+ )
+ ]
+ end
+
+ context 'when sentry_project_id is not set' do
+ before do
+ setting.update!(sentry_project_id: nil)
+
+ allow(sentry_client).to receive(:projects).and_return(sentry_projects)
+ allow(sentry_client).to receive(:update_issue).with(opts).and_return(true)
+ end
+
+ it 'tries to backfill it from sentry API' do
+ expect(result).to eq(updated: true)
+
+ expect(setting.reload.sentry_project_id).to eq(sentry_project_id)
+ end
+
+ context 'when the project cannot be found on sentry' do
+ before do
+ sentry_projects.pop
+ end
+
+ it 'raises error' do
+ expect { result }.to raise_error(/Couldn't find project/)
+ end
+ end
+ end
+
+ context 'when mismatching sentry_project_id is detected' do
+ it 'raises error' do
+ setting.update!(sentry_project_id: sentry_project_id + 1)
+
+ expect { result }.to raise_error(/The Sentry issue appers to be outside/)
+ end
+ end
+ end
end
describe 'slugs' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 196254d4609..48bd3bdf7eb 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -4057,6 +4057,41 @@ RSpec.describe User do
end
end
+ describe '#authorized_project_mirrors' do
+ it 'returns project mirrors where the user has access equal to or above the given level' do
+ guest_project = create(:project)
+ reporter_project = create(:project)
+ maintainer_project = create(:project)
+
+ guest_group = create(:group)
+ reporter_group = create(:group)
+ maintainer_group = create(:group)
+
+ _guest_group_project = create(:project, group: guest_group)
+ reporter_group_project = create(:project, group: reporter_group)
+ maintainer_group_project = create(:project, group: maintainer_group)
+
+ user = create(:user)
+
+ guest_project.add_guest(user)
+ reporter_project.add_reporter(user)
+ maintainer_project.add_maintainer(user)
+
+ guest_group.add_guest(user)
+ reporter_group.add_reporter(user)
+ maintainer_group.add_maintainer(user)
+
+ project_mirrors = user.authorized_project_mirrors(Gitlab::Access::REPORTER)
+
+ expect(project_mirrors.pluck(:project_id)).to contain_exactly(
+ reporter_group_project.id,
+ maintainer_group_project.id,
+ reporter_project.id,
+ maintainer_project.id
+ )
+ end
+ end
+
shared_context '#ci_owned_runners' do
let(:user) { create(:user) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index eaace86025c..5884dc3f23f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -2361,4 +2361,25 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:register_project_runners) }
end
end
+
+ describe 'update_sentry_issue' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:role, :allowed) do
+ :owner | true
+ :maintainer | true
+ :developer | true
+ :reporter | false
+ :guest | false
+ end
+
+ let(:project) { public_project }
+ let(:current_user) { public_send(role) }
+
+ with_them do
+ it do
+ expect(subject.can?(:update_sentry_issue)).to be(allowed)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index 3000bdc2ce7..31b85a0b1d6 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -804,6 +804,23 @@ RSpec.describe API::Ci::Runners do
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(2)
end
+
+ context 'when user does not have authorization to see all jobs' do
+ it 'shows only jobs it has permission to see' do
+ create(:ci_build, :running, runner: two_projects_runner, project: project)
+ create(:ci_build, :running, runner: two_projects_runner, project: project2)
+
+ project.add_guest(user2)
+ project2.add_maintainer(user2)
+ get api("/runners/#{two_projects_runner.id}/jobs", user2)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+
+ expect(json_response).to be_an(Array)
+ expect(json_response.length).to eq(1)
+ end
+ end
end
context 'when valid status is provided' do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 48f2c45bd98..c1217292d5c 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -483,6 +483,29 @@ RSpec.describe API::Labels do
let(:request) { api("/projects/#{project.id}/labels", user) }
let(:params) { { name: valid_label_title_1 } }
end
+
+ context 'with group label' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
+
+ before do
+ project.update!(group: group)
+ end
+
+ it 'returns 401 if user does not have access' do
+ delete api("/projects/#{project.id}/labels/#{group_label.id}", user)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 204 if user has access' do
+ group.add_developer(user)
+
+ delete api("/projects/#{project.id}/labels/#{group_label.id}", user)
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+ end
end
describe 'PUT /projects/:id/labels' do
@@ -537,6 +560,44 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:bad_request)
end
+
+ context 'with group label' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
+
+ before do
+ project.update!(group: group)
+ end
+
+ it 'allows updating of group label priority' do
+ put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: { priority: 5 }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['priority']).to eq(5)
+ end
+
+ it 'returns 401 when updating other fields' do
+ put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: {
+ priority: 5,
+ new_name: 'new label name'
+ }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'returns 200 when user has access to the group label' do
+ group.add_developer(user)
+
+ put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: {
+ priority: 5,
+ new_name: 'new label name'
+ }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['priority']).to eq(5)
+ expect(json_response['name']).to eq('new label name')
+ end
+ end
end
describe 'PUT /projects/:id/labels/promote' do
diff --git a/spec/requests/jira_routing_spec.rb b/spec/requests/jira_routing_spec.rb
index a627eea33a8..e0e170044de 100644
--- a/spec/requests/jira_routing_spec.rb
+++ b/spec/requests/jira_routing_spec.rb
@@ -25,27 +25,49 @@ RSpec.describe 'Jira referenced paths', type: :request do
expect(response).to redirect_to(redirect_path)
end
- context 'with encoded subgroup path' do
- where(:jira_path, :redirect_path) do
- '/group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project'
- '/group@sub_group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project'
- '/group/group@sub_group@sub_group_project/commit/1234567' | '/group/sub_group/sub_group_project/commit/1234567'
- '/group/group@sub_group@sub_group_project/tree/1234567' | '/group/sub_group/sub_group_project/-/tree/1234567'
+ shared_examples 'redirects to jira path' do
+ it 'redirects to canonical path with legacy prefix' do
+ redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path
end
- with_them do
- context 'with legacy prefix' do
- it 'redirects to canonical path' do
- redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path
- end
- end
-
- it 'redirects to canonical path' do
- redirects_to_canonical_path jira_path, redirect_path
- end
+ it 'redirects to canonical path' do
+ redirects_to_canonical_path jira_path, redirect_path
end
end
+ let(:jira_path) { '/group/group@sub_group@sub_group_project' }
+ let(:redirect_path) { '/group/sub_group/sub_group_project' }
+
+ it_behaves_like 'redirects to jira path'
+
+ context 'contains @ before the first /' do
+ let(:jira_path) { '/group@sub_group/group@sub_group@sub_group_project' }
+ let(:redirect_path) { '/group/sub_group/sub_group_project' }
+
+ it_behaves_like 'redirects to jira path'
+ end
+
+ context 'including commit path' do
+ let(:jira_path) { '/group/group@sub_group@sub_group_project/commit/1234567' }
+ let(:redirect_path) { '/group/sub_group/sub_group_project/commit/1234567' }
+
+ it_behaves_like 'redirects to jira path'
+ end
+
+ context 'including tree path' do
+ let(:jira_path) { '/group/group@sub_group@sub_group_project/tree/1234567' }
+ let(:redirect_path) { '/group/sub_group/sub_group_project/-/tree/1234567' }
+
+ it_behaves_like 'redirects to jira path'
+ end
+
+ context 'malicious path' do
+ let(:jira_path) { '/group/@@malicious.server' }
+ let(:redirect_path) { '/malicious.server' }
+
+ it_behaves_like 'redirects to jira path'
+ end
+
context 'regular paths with legacy prefix' do
where(:jira_path, :redirect_path) do
'/-/jira/group/group_project' | '/group/group_project'
diff --git a/spec/serializers/member_user_entity_spec.rb b/spec/serializers/member_user_entity_spec.rb
index 0e6d4bcc3fb..85f29845d65 100644
--- a/spec/serializers/member_user_entity_spec.rb
+++ b/spec/serializers/member_user_entity_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe MemberUserEntity do
let(:entity_hash) { entity.as_json }
it 'matches json schema' do
- expect(entity.to_json).to match_schema('entities/member_user')
+ expect(entity.to_json).to match_schema('entities/member_user_default')
end
it 'correctly exposes `avatar_url`' do
@@ -27,10 +27,8 @@ RSpec.describe MemberUserEntity do
expect(entity_hash[:blocked]).to be(true)
end
- it 'correctly exposes `two_factor_enabled`' do
- allow(user).to receive(:two_factor_enabled?).and_return(true)
-
- expect(entity_hash[:two_factor_enabled]).to be(true)
+ it 'does not expose `two_factor_enabled` by default' do
+ expect(entity_hash[:two_factor_enabled]).to be(nil)
end
it 'correctly exposes `status.emoji`' do
@@ -44,4 +42,66 @@ RSpec.describe MemberUserEntity do
it 'correctly exposes `last_activity_on`' do
expect(entity_hash[:last_activity_on]).to be(user.last_activity_on)
end
+
+ context 'when options includes a source' do
+ let(:current_user) { create(:user) }
+ let(:options) { { current_user: current_user, source: source } }
+ let(:entity) { described_class.new(user, options) }
+
+ shared_examples 'correctly exposes user two_factor_enabled' do
+ context 'when the current_user has a role lower than minimum manage member role' do
+ before do
+ source.add_user(current_user, Gitlab::Access::DEVELOPER)
+ end
+
+ it 'does not expose user two_factor_enabled' do
+ expect(entity_hash[:two_factor_enabled]).to be(nil)
+ end
+
+ it 'matches json schema' do
+ expect(entity.to_json).to match_schema('entities/member_user_default')
+ end
+ end
+
+ context 'when the current user has a minimum manage member role or higher' do
+ before do
+ source.add_user(current_user, minimum_manage_member_role)
+ end
+
+ it 'matches json schema' do
+ expect(entity.to_json).to match_schema('entities/member_user_for_admin_member')
+ end
+
+ it 'exposes user two_factor_enabled' do
+ expect(entity_hash[:two_factor_enabled]).to be(false)
+ end
+ end
+
+ context 'when the current user is self' do
+ let(:current_user) { user }
+
+ it 'exposes user two_factor_enabled' do
+ expect(entity_hash[:two_factor_enabled]).to be(false)
+ end
+
+ it 'matches json schema' do
+ expect(entity.to_json).to match_schema('entities/member_user_for_admin_member')
+ end
+ end
+ end
+
+ context 'when the source is a group' do
+ let(:source) { create(:group) }
+ let(:minimum_manage_member_role) { Gitlab::Access::OWNER }
+
+ it_behaves_like 'correctly exposes user two_factor_enabled'
+ end
+
+ context 'when the source is a project' do
+ let(:source) { create(:project) }
+ let(:minimum_manage_member_role) { Gitlab::Access::MAINTAINER }
+
+ it_behaves_like 'correctly exposes user two_factor_enabled'
+ end
+ end
end
diff --git a/spec/services/bulk_imports/file_decompression_service_spec.rb b/spec/services/bulk_imports/file_decompression_service_spec.rb
index 1d6aa79a37f..77348428d60 100644
--- a/spec/services/bulk_imports/file_decompression_service_spec.rb
+++ b/spec/services/bulk_imports/file_decompression_service_spec.rb
@@ -80,7 +80,8 @@ RSpec.describe BulkImports::FileDecompressionService do
subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') }
it 'raises an error and removes the file' do
- expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file')
+ expect { subject.execute }
+ .to raise_error(BulkImports::FileDecompressionService::ServiceError, 'File decompression error')
expect(File.exist?(symlink)).to eq(false)
end
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 914b3d3b867..ba2a50c6edb 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -303,7 +303,7 @@ RSpec.describe Issues::CreateService do
context 'user is reporter or above' do
before do
- project.add_reporter(user)
+ project.add_developer(user)
end
it 'assigns the sentry error' do
diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
index e8ccb12e6b7..e7360de34f6 100644
--- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
+++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb
@@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do
before do
allow(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
- project.add_reporter(user)
+ project.add_developer(user)
end
end
diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
index 135fa4cf5a4..e6b0772aec1 100644
--- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb
@@ -19,33 +19,66 @@ RSpec.shared_examples 'conan ping endpoint' do
end
RSpec.shared_examples 'conan search endpoint' do
- before do
- project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
-
- # Do not pass the HTTP_AUTHORIZATION header,
- # in order to test that this public project's packages
- # are visible to anonymous search.
- get api(url), params: params
- end
+ using RSpec::Parameterized::TableSyntax
subject { json_response['results'] }
- context 'returns packages with a matching name' do
- let(:params) { { q: package.conan_recipe } }
+ context 'with a public project' do
+ before do
+ project.update!(visibility: 'public')
+
+ # Do not pass the HTTP_AUTHORIZATION header,
+ # in order to test that this public project's packages
+ # are visible to anonymous search.
+ get api(url), params: params
+ end
+
+ context 'returns packages with a matching name' do
+ let(:params) { { q: package.conan_recipe } }
+
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'returns packages using a * wildcard' do
+ let(:params) { { q: "#{package.name[0, 3]}*" } }
- it { is_expected.to contain_exactly(package.conan_recipe) }
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ end
+
+ context 'does not return non-matching packages' do
+ let(:params) { { q: "foo" } }
+
+ it { is_expected.to be_blank }
+ end
end
- context 'returns packages using a * wildcard' do
+ context 'with a private project' do
let(:params) { { q: "#{package.name[0, 3]}*" } }
- it { is_expected.to contain_exactly(package.conan_recipe) }
- end
+ where(:role, :packages_visible) do
+ :maintainer | true
+ :developer | true
+ :reporter | true
+ :guest | false
+ :anonymous | false
+ end
- context 'does not return non-matching packages' do
- let(:params) { { q: "foo" } }
+ with_them do
+ before do
+ project.update!(visibility: 'private')
+ project.team.truncate
+ user.project_authorizations.delete_all
+ project.add_user(user, role) unless role == :anonymous
+
+ get api(url), params: params, headers: headers
+ end
- it { is_expected.to be_blank }
+ if params[:packages_visible]
+ it { is_expected.to contain_exactly(package.conan_recipe) }
+ else
+ it { is_expected.to be_blank }
+ end
+ end
end
end
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 5ccd0d2efdb..f6ea0bc7307 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -259,6 +259,7 @@ RSpec.describe 'Every Sidekiq worker' do
'Gitlab::GithubImport::AdvanceStageWorker' => 3,
'Gitlab::GithubImport::ImportDiffNoteWorker' => 5,
'Gitlab::GithubImport::ImportIssueWorker' => 5,
+ 'Gitlab::GithubImport::ImportIssueEventWorker' => 5,
'Gitlab::GithubImport::ImportLfsObjectWorker' => 5,
'Gitlab::GithubImport::ImportNoteWorker' => 5,
'Gitlab::GithubImport::ImportPullRequestMergedByWorker' => 5,
@@ -268,6 +269,7 @@ RSpec.describe 'Every Sidekiq worker' do
'Gitlab::GithubImport::Stage::FinishImportWorker' => 5,
'Gitlab::GithubImport::Stage::ImportBaseDataWorker' => 5,
'Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker' => 5,
+ 'Gitlab::GithubImport::Stage::ImportIssueEventsWorker' => 5,
'Gitlab::GithubImport::Stage::ImportLfsObjectsWorker' => 5,
'Gitlab::GithubImport::Stage::ImportNotesWorker' => 5,
'Gitlab::GithubImport::Stage::ImportPullRequestsMergedByWorker' => 5,
diff --git a/spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb
new file mode 100644
index 00000000000..6af450151e3
--- /dev/null
+++ b/spec/workers/gitlab/github_import/import_issue_event_worker_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::ImportIssueEventWorker do
+ subject(:worker) { described_class.new }
+
+ describe '#import' do
+ let(:project) do
+ instance_double('Project', full_path: 'foo/bar', id: 1, import_state: nil)
+ end
+
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+ let(:importer) { instance_double('Gitlab::GithubImport::Importer::IssueEventImporter') }
+
+ let(:event_hash) do
+ {
+ 'id' => 6501124486,
+ 'node_id' => 'CE_lADOHK9fA85If7x0zwAAAAGDf0mG',
+ 'url' => 'https://api.github.com/repos/elhowm/test-import/issues/events/6501124486',
+ 'actor' => { 'id' => 4, 'login' => 'alice' },
+ 'event' => 'closed',
+ 'commit_id' => nil,
+ 'commit_url' => nil,
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'performed_via_github_app' => nil
+ }
+ end
+
+ it 'imports an issue event' do
+ expect(Gitlab::GithubImport::Importer::IssueEventImporter)
+ .to receive(:new)
+ .with(
+ an_instance_of(Gitlab::GithubImport::Representation::IssueEvent),
+ project,
+ client
+ )
+ .and_return(importer)
+
+ expect(importer).to receive(:execute)
+
+ expect(Gitlab::GithubImport::ObjectCounter)
+ .to receive(:increment)
+ .and_call_original
+
+ worker.import(project, client, event_hash)
+ end
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
new file mode 100644
index 00000000000..b3c6a48767c
--- /dev/null
+++ b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Stage::ImportIssueEventsWorker do
+ subject(:worker) { described_class.new }
+
+ let(:project) { create(:project) }
+ let!(:group) { create(:group, projects: [project]) }
+ let(:feature_flag_state) { [group] }
+
+ describe '#import' do
+ let(:importer) { instance_double('Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter') }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
+
+ before do
+ stub_feature_flags(github_importer_issue_events_import: feature_flag_state)
+ end
+
+ it 'imports all the issue events' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer).to receive(:execute).and_return(waiter)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :notes)
+
+ worker.import(client, project)
+ end
+
+ context 'when feature flag is disabled' do
+ let(:feature_flag_state) { false }
+
+ it 'skips issue events import and calls next stage' do
+ expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::AdvanceStageWorker).to receive(:perform_async).with(project.id, {}, :notes)
+
+ worker.import(client, project)
+ end
+ end
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
index c0dd4f488cc..a88256b3cae 100644
--- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
@@ -26,7 +26,7 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
expect(Gitlab::GithubImport::AdvanceStageWorker)
.to receive(:perform_async)
- .with(project.id, { '123' => 2 }, :notes)
+ .with(project.id, { '123' => 2 }, :issue_events)
worker.import(client, project)
end