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>2024-01-05 21:21:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-05 21:21:08 +0300
commit534ce3b2d0a6ec24de9c370e5b85c9528ff63e34 (patch)
tree4ad964818b181fddc0925e33b63f9b1f2ded23d3 /spec
parent4ba8ae97071935c39216afc53304c60386bbfa68 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/projects.rb33
-rw-r--r--spec/finders/ci/runner_jobs_finder_spec.rb88
-rw-r--r--spec/frontend/organizations/new/components/app_spec.js27
-rw-r--r--spec/frontend/organizations/settings/general/components/organization_settings_spec.js14
-rw-r--r--spec/frontend/organizations/shared/components/new_edit_form_spec.js34
-rw-r--r--spec/frontend/organizations/show/components/app_spec.js7
-rw-r--r--spec/frontend/organizations/show/components/organization_description_spec.js46
-rw-r--r--spec/helpers/organizations/organization_helper_spec.rb13
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb16
-rw-r--r--spec/models/ci/build_spec.rb52
-rw-r--r--spec/models/ci/runner_manager_spec.rb273
-rw-r--r--spec/models/organizations/organization_detail_spec.rb9
-rw-r--r--spec/models/organizations/organization_spec.rb1
-rw-r--r--spec/requests/api/ci/runners_spec.rb281
-rw-r--r--spec/requests/organizations/organizations_controller_spec.rb24
-rw-r--r--spec/routing/organizations/organizations_controller_routing_spec.rb5
-rw-r--r--spec/rubocop/cop/scalability/file_uploads_spec.rb2
-rw-r--r--spec/support/helpers/debug_with_puts.rb13
-rw-r--r--spec/support/helpers/stub_requests.rb18
19 files changed, 672 insertions, 284 deletions
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index a2848bd0256..83107e6cc4a 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -2,6 +2,10 @@
require_relative '../support/helpers/test_env'
+# TODO: Remove the debug_with_puts statements below! Used for debugging purposes.
+# TODO: https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/323#note_1688925316
+require_relative '../support/helpers/debug_with_puts'
+
FactoryBot.define do
# Project without repository
#
@@ -66,6 +70,8 @@ FactoryBot.define do
end
after(:build) do |project, evaluator|
+ DebugWithPuts.debug_with_puts "Beginning of after :build of projects factory in spec/factories/projects.rb"
+
# Builds and MRs can't have higher visibility level than repository access level.
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
@@ -87,6 +93,8 @@ FactoryBot.define do
security_and_compliance_access_level: evaluator.security_and_compliance_access_level
}
+ DebugWithPuts.debug_with_puts "During after :build of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
project_namespace_hash = {
name: evaluator.name,
path: evaluator.path,
@@ -97,10 +105,16 @@ FactoryBot.define do
project_namespace_hash[:id] = evaluator.project_namespace_id.presence
+ DebugWithPuts.debug_with_puts "During after :build of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
project.build_project_namespace(project_namespace_hash)
project.build_project_feature(project_feature_hash)
+ DebugWithPuts.debug_with_puts "During after :build of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
project.set_runners_token(evaluator.runners_token) if evaluator.runners_token.present?
+
+ DebugWithPuts.debug_with_puts "End of after :build of projects factory in spec/factories/projects.rb"
end
to_create do |project|
@@ -108,6 +122,7 @@ FactoryBot.define do
end
after(:create) do |project, evaluator|
+ DebugWithPuts.debug_with_puts "Beginning of after :create of projects factory in spec/factories/projects.rb"
# Normally the class Projects::CreateService is used for creating
# projects, and this class takes care of making sure the owner and current
# user have access to the project. Our specs don't use said service class,
@@ -116,12 +131,16 @@ FactoryBot.define do
project.add_owner(project.first_owner)
end
+ DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
if project.group
project.run_after_commit_or_now do
AuthorizedProjectUpdate::ProjectRecalculateService.new(project).execute
end
end
+ DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
# assign the delegated `#ci_cd_settings` attributes after create
project.group_runners_enabled = evaluator.group_runners_enabled unless evaluator.group_runners_enabled.nil?
project.merge_pipelines_enabled = evaluator.merge_pipelines_enabled unless evaluator.merge_pipelines_enabled.nil?
@@ -133,6 +152,8 @@ FactoryBot.define do
project.runner_token_expiration_interval = evaluator.runner_token_expiration_interval unless evaluator.runner_token_expiration_interval.nil?
project.runner_token_expiration_interval_human_readable = evaluator.runner_token_expiration_interval_human_readable unless evaluator.runner_token_expiration_interval_human_readable.nil?
+ DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
if evaluator.import_status
import_state = project.import_state || project.build_import_state
import_state.status = evaluator.import_status
@@ -142,8 +163,12 @@ FactoryBot.define do
import_state.save!
end
+ DebugWithPuts.debug_with_puts "During after :create of projects factory in spec/factories/projects.rb:#{__LINE__}"
+
# simulating ::Projects::ProcessSyncEventsWorker because most tests don't run Sidekiq inline
project.create_ci_project_mirror!(namespace_id: project.namespace_id) unless project.ci_project_mirror
+
+ DebugWithPuts.debug_with_puts "End of after :create of projects factory in spec/factories/projects.rb"
end
trait :public do
@@ -326,6 +351,7 @@ FactoryBot.define do
end
after :create do |project, evaluator|
+ DebugWithPuts.debug_with_puts "Beginning of after :create of trait :repository do in spec/factories/projects.rb"
# Specify `lfs: true` to create the LfsObject for the LFS file in the test repo:
# https://gitlab.com/gitlab-org/gitlab-test/-/blob/master/files/lfs/lfs_object.iso
if evaluator.lfs
@@ -351,6 +377,8 @@ FactoryBot.define do
end
end
+ DebugWithPuts.debug_with_puts "During after :create of trait :repository do in spec/factories/projects.rb:#{__LINE__}"
+
if evaluator.create_templates
templates_path = "#{evaluator.create_templates}_templates"
@@ -380,6 +408,8 @@ FactoryBot.define do
branch_name: 'master')
end
+ DebugWithPuts.debug_with_puts "During after :create of trait :repository do in spec/factories/projects.rb:#{__LINE__}"
+
if evaluator.create_branch
project.repository.create_file(
project.creator,
@@ -389,6 +419,8 @@ FactoryBot.define do
branch_name: evaluator.create_branch)
end
+ DebugWithPuts.debug_with_puts "During after :create of trait :repository do in spec/factories/projects.rb:#{__LINE__}"
+
if evaluator.create_tag
project.repository.add_tag(
project.creator,
@@ -397,6 +429,7 @@ FactoryBot.define do
end
project.track_project_repository
+ DebugWithPuts.debug_with_puts "End of after :create of trait :repository do in spec/factories/projects.rb"
end
end
diff --git a/spec/finders/ci/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb
index 755b21ec08f..66cdde756be 100644
--- a/spec/finders/ci/runner_jobs_finder_spec.rb
+++ b/spec/finders/ci/runner_jobs_finder_spec.rb
@@ -2,26 +2,28 @@
require 'spec_helper'
-RSpec.describe Ci::RunnerJobsFinder do
- let(:project) { create(:project) }
- let(:runner) { create(:ci_runner, :instance) }
- let(:user) { create(:user) }
+RSpec.describe Ci::RunnerJobsFinder, feature_category: :fleet_visibility do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:runner) { create(:ci_runner, :instance) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:runner_manager) { create(:ci_runner_machine, runner: runner) }
+ let_it_be(:jobs) { create_list(:ci_build, 5, runner_manager: runner_manager, project: project) }
+
let(:params) { {} }
- subject { described_class.new(runner, user, params).execute }
+ subject(:returned_jobs) { described_class.new(runner, user, params).execute }
- before do
+ before_all do
project.add_developer(user)
end
describe '#execute' do
context 'when params is empty' do
- let!(:job) { create(:ci_build, runner: runner, project: project) }
let!(:job1) { create(:ci_build, project: project) }
it 'returns all jobs assigned to Runner' do
- is_expected.to match_array(job)
- is_expected.not_to match_array(job1)
+ is_expected.to match_array(jobs)
+ is_expected.not_to include(job1)
end
end
@@ -36,35 +38,34 @@ RSpec.describe Ci::RunnerJobsFinder do
end
end
- context 'when the user has permission to read all resources' do
- let(:user) { create(:user, :admin) }
+ context 'when the user is admin', :enable_admin_mode do
+ let_it_be(:user) { create(:user, :admin) }
- it 'returns all the jobs assigned to a runner' do
- jobs = create_list(:ci_build, 5, runner: runner, project: project)
+ it { is_expected.to match_array(jobs) }
+ end
- is_expected.to match_array(jobs)
+ context 'when user is developer' do
+ before_all do
+ project.add_developer(user)
end
+
+ it { is_expected.to match_array(jobs) }
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)
+ let_it_be(:guest_project) { create(:project).tap { |p| p.add_guest(user) } }
+ let_it_be(:guest_jobs) { create_list(:ci_build, 2, runner: runner, project: guest_project) }
+ let_it_be(:reporter_project) { create(:project).tap { |p| p.add_reporter(user) } }
+ let_it_be(:reporter_jobs) { create_list(:ci_build, 3, runner: runner, project: reporter_project) }
+
+ it 'returns only the jobs the user has permission to see', :aggregate_failures do
+ is_expected.to include(*reporter_jobs)
+ is_expected.not_to include(*guest_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)
-
+ it 'returns jobs assigned to the Runner that the user has access to' do
is_expected.to match_array(jobs)
end
end
@@ -73,24 +74,38 @@ RSpec.describe Ci::RunnerJobsFinder do
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do
let(:params) { { status: target_status } }
+ let(:exception_status) { (Ci::HasStatus::AVAILABLE_STATUSES - [target_status]).first }
let!(:job) { create(:ci_build, runner: runner, project: project, status: target_status) }
+ let!(:other_job) { create(:ci_build, runner: runner, project: project, status: exception_status) }
- before do
- exception_status = Ci::HasStatus::AVAILABLE_STATUSES - [target_status]
- create(:ci_build, runner: runner, project: project, status: exception_status.first)
- end
-
- it 'returns matched job' do
- is_expected.to eq([job])
+ it 'returns matched job', :aggregate_failures do
+ is_expected.to include(job)
+ is_expected.not_to include(other_job)
end
end
end
end
+ context 'when system_id is specified' do
+ let_it_be(:runner_manager2) { create(:ci_runner_machine, runner: runner) }
+ let_it_be(:job2) { create(:ci_build, runner_manager: runner_manager2, project: project) }
+
+ let(:params) { { system_id: runner_manager.system_xid } }
+
+ it 'returns jobs from the specified system' do
+ expect(returned_jobs).to match_array(jobs)
+ end
+
+ context 'when specified system_id does not exist' do
+ let(:params) { { system_id: 'unknown_system' } }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
context 'when order_by and sort are specified' do
context 'when order_by id and sort is asc' do
let(:params) { { order_by: 'id', sort: 'asc' } }
- let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
it 'sorts as id: :asc' do
is_expected.to eq(jobs.sort_by(&:id))
@@ -101,7 +116,6 @@ RSpec.describe Ci::RunnerJobsFinder do
context 'when order_by is specified and sort is not specified' do
context 'when order_by id and sort is not specified' do
let(:params) { { order_by: 'id' } }
- let!(:jobs) { create_list(:ci_build, 2, runner: runner, project: project, user: create(:user)) }
it 'sorts as id: :desc' do
is_expected.to eq(jobs.sort_by(&:id).reverse)
diff --git a/spec/frontend/organizations/new/components/app_spec.js b/spec/frontend/organizations/new/components/app_spec.js
index 4f31baedbf6..e3e1c5b9684 100644
--- a/spec/frontend/organizations/new/components/app_spec.js
+++ b/spec/frontend/organizations/new/components/app_spec.js
@@ -24,10 +24,14 @@ describe('OrganizationNewApp', () => {
let wrapper;
let mockApollo;
+ const file = new File(['foo'], 'foo.jpg', {
+ type: 'text/plain',
+ });
+
+ const successfulResponseHandler = jest.fn().mockResolvedValue(organizationCreateResponse);
+
const createComponent = ({
- handlers = [
- [organizationCreateMutation, jest.fn().mockResolvedValue(organizationCreateResponse)],
- ],
+ handlers = [[organizationCreateMutation, successfulResponseHandler]],
} = {}) => {
mockApollo = createMockApollo(handlers);
@@ -36,7 +40,12 @@ describe('OrganizationNewApp', () => {
const findForm = () => wrapper.findComponent(NewEditForm);
const submitForm = async () => {
- findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar' });
+ findForm().vm.$emit('submit', {
+ name: 'Foo bar',
+ path: 'foo-bar',
+ description: 'Foo bar description',
+ avatar: file,
+ });
await nextTick();
};
@@ -74,7 +83,15 @@ describe('OrganizationNewApp', () => {
await waitForPromises();
});
- it('redirects user to organization web url', () => {
+ it('calls mutation with correct variables and redirects user to organization web url', () => {
+ expect(successfulResponseHandler).toHaveBeenCalledWith({
+ input: {
+ name: 'Foo bar',
+ path: 'foo-bar',
+ description: 'Foo bar description',
+ avatar: file,
+ },
+ });
expect(visitUrlWithAlerts).toHaveBeenCalledWith(
organizationCreateResponse.data.organizationCreate.organization.webUrl,
[
diff --git a/spec/frontend/organizations/settings/general/components/organization_settings_spec.js b/spec/frontend/organizations/settings/general/components/organization_settings_spec.js
index eca6d9fdc4a..52e81d7fb5d 100644
--- a/spec/frontend/organizations/settings/general/components/organization_settings_spec.js
+++ b/spec/frontend/organizations/settings/general/components/organization_settings_spec.js
@@ -9,6 +9,7 @@ import {
FORM_FIELD_NAME,
FORM_FIELD_ID,
FORM_FIELD_AVATAR,
+ FORM_FIELD_DESCRIPTION,
} from '~/organizations/shared/constants';
import organizationUpdateMutation from '~/organizations/settings/general/graphql/mutations/organization_update.mutation.graphql';
import {
@@ -62,7 +63,13 @@ describe('OrganizationSettings', () => {
const findForm = () => wrapper.findComponent(NewEditForm);
const submitForm = async (data = {}) => {
- findForm().vm.$emit('submit', { name: 'Foo bar', path: 'foo-bar', avatar: file, ...data });
+ findForm().vm.$emit('submit', {
+ name: 'Foo bar',
+ path: 'foo-bar',
+ description: 'Foo bar description',
+ avatar: file,
+ ...data,
+ });
await nextTick();
};
@@ -84,7 +91,7 @@ describe('OrganizationSettings', () => {
expect(findForm().props()).toMatchObject({
loading: false,
initialFormValues: defaultProvide.organization,
- fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_AVATAR],
+ fieldsToRender: [FORM_FIELD_NAME, FORM_FIELD_ID, FORM_FIELD_DESCRIPTION, FORM_FIELD_AVATAR],
});
});
@@ -117,6 +124,7 @@ describe('OrganizationSettings', () => {
input: {
id: 'gid://gitlab/Organizations::Organization/1',
name: 'Foo bar',
+ description: 'Foo bar description',
avatar: file,
},
});
@@ -191,6 +199,7 @@ describe('OrganizationSettings', () => {
input: {
id: 'gid://gitlab/Organizations::Organization/1',
name: 'Foo bar',
+ description: 'Foo bar description',
avatar: null,
},
});
@@ -208,6 +217,7 @@ describe('OrganizationSettings', () => {
input: {
id: 'gid://gitlab/Organizations::Organization/1',
name: 'Foo bar',
+ description: 'Foo bar description',
},
});
});
diff --git a/spec/frontend/organizations/shared/components/new_edit_form_spec.js b/spec/frontend/organizations/shared/components/new_edit_form_spec.js
index 4897a81fc1c..5be26ef7cc3 100644
--- a/spec/frontend/organizations/shared/components/new_edit_form_spec.js
+++ b/spec/frontend/organizations/shared/components/new_edit_form_spec.js
@@ -1,9 +1,10 @@
-import { GlButton } from '@gitlab/ui';
import { nextTick } from 'vue';
import NewEditForm from '~/organizations/shared/components/new_edit_form.vue';
import OrganizationUrlField from '~/organizations/shared/components/organization_url_field.vue';
import AvatarUploadDropzone from '~/vue_shared/components/upload_dropzone/avatar_upload_dropzone.vue';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+import { helpPagePath } from '~/helpers/help_page_helper';
import {
FORM_FIELD_NAME,
FORM_FIELD_ID,
@@ -18,6 +19,7 @@ describe('NewEditForm', () => {
const defaultProvide = {
organizationsPath: '/-/organizations',
rootUrl: 'http://127.0.0.1:3000/',
+ previewMarkdownPath: '/-/organizations/preview_markdown',
};
const defaultPropsData = {
@@ -38,6 +40,7 @@ describe('NewEditForm', () => {
const findNameField = () => wrapper.findByLabelText('Organization name');
const findIdField = () => wrapper.findByLabelText('Organization ID');
const findUrlField = () => wrapper.findComponent(OrganizationUrlField);
+ const findDescriptionField = () => wrapper.findByLabelText('Organization description (optional)');
const findAvatarField = () => wrapper.findComponent(AvatarUploadDropzone);
const setUrlFieldValue = async (value) => {
@@ -70,6 +73,30 @@ describe('NewEditForm', () => {
});
});
+ it('renders `Organization description` field as markdown editor', () => {
+ createComponent();
+
+ expect(findDescriptionField().exists()).toBe(true);
+ expect(wrapper.findComponent(MarkdownField).props()).toMatchObject({
+ markdownPreviewPath: defaultProvide.previewMarkdownPath,
+ markdownDocsPath: helpPagePath('user/organization/index', {
+ anchor: 'organization-description-supported-markdown',
+ }),
+ textareaValue: '',
+ restrictedToolBarItems: [
+ 'code',
+ 'quote',
+ 'bullet-list',
+ 'numbered-list',
+ 'task-list',
+ 'collapsible-section',
+ 'table',
+ 'attach-file',
+ 'full-screen',
+ ],
+ });
+ });
+
describe('when `Organization avatar` field is changed', () => {
const file = new File(['foo'], 'foo.jpg', {
type: 'text/plain',
@@ -154,12 +181,13 @@ describe('NewEditForm', () => {
await findNameField().setValue('Foo bar');
await setUrlFieldValue('foo-bar');
+ await findDescriptionField().setValue('Foo bar description');
await submitForm();
});
it('emits `submit` event with form values', () => {
expect(wrapper.emitted('submit')).toEqual([
- [{ name: 'Foo bar', path: 'foo-bar', avatar: null }],
+ [{ name: 'Foo bar', path: 'foo-bar', description: 'Foo bar description', avatar: null }],
]);
});
});
@@ -221,7 +249,7 @@ describe('NewEditForm', () => {
});
it('shows button with loading icon', () => {
- expect(wrapper.findComponent(GlButton).props('loading')).toBe(true);
+ expect(wrapper.findByTestId('submit-button').props('loading')).toBe(true);
});
});
diff --git a/spec/frontend/organizations/show/components/app_spec.js b/spec/frontend/organizations/show/components/app_spec.js
index 46496e40bdd..6cf8845bdbe 100644
--- a/spec/frontend/organizations/show/components/app_spec.js
+++ b/spec/frontend/organizations/show/components/app_spec.js
@@ -1,6 +1,7 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import App from '~/organizations/show/components/app.vue';
import OrganizationAvatar from '~/organizations/show/components/organization_avatar.vue';
+import OrganizationDescription from '~/organizations/show/components/organization_description.vue';
import GroupsAndProjects from '~/organizations/show/components/groups_and_projects.vue';
import AssociationCount from '~/organizations/show/components/association_counts.vue';
@@ -34,6 +35,12 @@ describe('OrganizationShowApp', () => {
);
});
+ it('renders organization description and passes organization prop', () => {
+ expect(wrapper.findComponent(OrganizationDescription).props('organization')).toEqual(
+ defaultPropsData.organization,
+ );
+ });
+
it('renders groups and projects component and passes `groupsAndProjectsOrganizationPath` prop', () => {
expect(
wrapper.findComponent(GroupsAndProjects).props('groupsAndProjectsOrganizationPath'),
diff --git a/spec/frontend/organizations/show/components/organization_description_spec.js b/spec/frontend/organizations/show/components/organization_description_spec.js
new file mode 100644
index 00000000000..2aaf6f24a72
--- /dev/null
+++ b/spec/frontend/organizations/show/components/organization_description_spec.js
@@ -0,0 +1,46 @@
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import OrganizationDescription from '~/organizations/show/components/organization_description.vue';
+
+describe('OrganizationDescription', () => {
+ let wrapper;
+
+ const defaultPropsData = {
+ organization: {
+ id: 1,
+ name: 'GitLab',
+ description_html: '<h1>Foo bar description</h1><script>alert("foo")</script>',
+ },
+ };
+
+ const createComponent = ({ propsData = {} } = {}) => {
+ wrapper = mountExtended(OrganizationDescription, {
+ propsData: { ...defaultPropsData, ...propsData },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ describe('when organization has description', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders description as safe HTML', () => {
+ expect(wrapper.element.innerHTML).toBe('<h1>Foo bar description</h1>');
+ });
+ });
+
+ describe('when organization does not have description', () => {
+ beforeEach(() => {
+ createComponent({
+ propsData: { organization: { ...defaultPropsData.organization, description_html: '' } },
+ });
+ });
+
+ it('renders nothing', () => {
+ expect(wrapper.html()).toBe('');
+ });
+ });
+});
diff --git a/spec/helpers/organizations/organization_helper_spec.rb b/spec/helpers/organizations/organization_helper_spec.rb
index a3613e29da9..0f2f4ed1b54 100644
--- a/spec/helpers/organizations/organization_helper_spec.rb
+++ b/spec/helpers/organizations/organization_helper_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
- let_it_be(:organization) { build_stubbed(:organization) }
+ let_it_be(:organization_detail) { build_stubbed(:organization_detail, description_html: '<em>description</em>') }
+ let_it_be(:organization) { organization_detail.organization }
let_it_be(:new_group_path) { '/groups/new' }
let_it_be(:new_project_path) { '/projects/new' }
let_it_be(:organizations_empty_state_svg_path) { 'illustrations/empty-state/empty-organizations-md.svg' }
@@ -11,6 +12,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
let_it_be(:root_url) { 'http://127.0.0.1:3000/' }
let_it_be(:groups_empty_state_svg_path) { 'illustrations/empty-state/empty-groups-md.svg' }
let_it_be(:projects_empty_state_svg_path) { 'illustrations/empty-state/empty-projects-md.svg' }
+ let_it_be(:preview_markdown_organizations_path) { '/-/organizations/preview_markdown' }
before do
allow(helper).to receive(:new_group_path).and_return(new_group_path)
@@ -21,6 +23,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
allow(helper).to receive(:root_url).and_return(root_url)
allow(helper).to receive(:image_path).with(groups_empty_state_svg_path).and_return(groups_empty_state_svg_path)
allow(helper).to receive(:image_path).with(projects_empty_state_svg_path).and_return(projects_empty_state_svg_path)
+ allow(helper).to receive(:preview_markdown_organizations_path).and_return(preview_markdown_organizations_path)
end
describe '#organization_show_app_data' do
@@ -41,6 +44,7 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
'organization' => {
'id' => organization.id,
'name' => organization.name,
+ 'description_html' => organization.description_html,
'avatar_url' => 'avatar.jpg'
},
'groups_and_projects_organization_path' => '/-/organizations/default/groups_and_projects',
@@ -91,7 +95,8 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
expect(Gitlab::Json.parse(helper.organization_new_app_data)).to eq(
{
'organizations_path' => organizations_path,
- 'root_url' => root_url
+ 'root_url' => root_url,
+ 'preview_markdown_path' => preview_markdown_organizations_path
}
)
end
@@ -119,10 +124,12 @@ RSpec.describe Organizations::OrganizationHelper, feature_category: :cell do
'id' => organization.id,
'name' => organization.name,
'path' => organization.path,
+ 'description' => organization.description,
'avatar' => 'avatar.jpg'
},
'organizations_path' => organizations_path,
- 'root_url' => root_url
+ 'root_url' => root_url,
+ 'preview_markdown_path' => preview_markdown_organizations_path
}
)
end
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index 127f437dd54..e3cddceb7a9 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -100,5 +100,21 @@ RSpec.describe Gitlab::DependencyLinker::PackageJsonLinker do
it 'does not link scripts with the same key as a package' do
expect(subject).not_to include(link('karma start config/karma.config.js --single-run', 'https://github.com/karma start config/karma.config.js --single-run'))
end
+
+ context 'when dependency is not a string' do
+ let(:file_content) do
+ <<-CONTENT.strip_heredoc
+ {
+ "dependencies": {
+ "wrong": {}
+ }
+ }
+ CONTENT
+ end
+
+ it 'does not link it' do
+ expect(subject).not_to include(%(<a href))
+ end
+ end
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index af3f54c5f0a..c5c97228988 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -325,14 +325,15 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
describe '.with_exposed_artifacts' do
subject { described_class.with_exposed_artifacts }
- let!(:job1) { create(:ci_build, pipeline: pipeline) }
+ let_it_be(:job1) { create(:ci_build, pipeline: pipeline) }
+ let_it_be(:job3) { create(:ci_build, pipeline: pipeline) }
+
let!(:job2) { create(:ci_build, options: options, pipeline: pipeline) }
- let!(:job3) { create(:ci_build, pipeline: pipeline) }
- context 'when some jobs have exposed artifacs and some not' do
+ context 'when some jobs have exposed artifacts and some not' do
let(:options) { { artifacts: { expose_as: 'test', paths: ['test'] } } }
- before do
+ before_all do
job1.ensure_metadata.update!(has_exposed_artifacts: nil)
job3.ensure_metadata.update!(has_exposed_artifacts: false)
end
@@ -356,10 +357,10 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
let(:artifact_scope) { Ci::JobArtifact.where(file_type: 'archive') }
- let!(:build_1) { create(:ci_build, :artifacts, pipeline: pipeline) }
- let!(:build_2) { create(:ci_build, :codequality_reports, pipeline: pipeline) }
- let!(:build_3) { create(:ci_build, :test_reports, pipeline: pipeline) }
- let!(:build_4) { create(:ci_build, :artifacts, pipeline: pipeline) }
+ let_it_be(:build_1) { create(:ci_build, :artifacts, pipeline: pipeline) }
+ let_it_be(:build_2) { create(:ci_build, :codequality_reports, pipeline: pipeline) }
+ let_it_be(:build_3) { create(:ci_build, :test_reports, pipeline: pipeline) }
+ let_it_be(:build_4) { create(:ci_build, :artifacts, pipeline: pipeline) }
it 'returns artifacts matching the given scope' do
expect(builds).to contain_exactly(build_1, build_4)
@@ -383,10 +384,10 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
describe '.with_needs' do
- let!(:build) { create(:ci_build, pipeline: pipeline) }
- let!(:build_b) { create(:ci_build, pipeline: pipeline) }
- let!(:build_need_a) { create(:ci_build_need, build: build) }
- let!(:build_need_b) { create(:ci_build_need, build: build_b) }
+ let_it_be(:build) { create(:ci_build, pipeline: pipeline) }
+ let_it_be(:build_b) { create(:ci_build, pipeline: pipeline) }
+ let_it_be(:build_need_a) { create(:ci_build_need, build: build) }
+ let_it_be(:build_need_b) { create(:ci_build_need, build: build_b) }
context 'when passing build name' do
subject { described_class.with_needs(build_need_a.name) }
@@ -421,6 +422,33 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def
end
end
+ describe '.belonging_to_runner_manager' do
+ subject { described_class.belonging_to_runner_manager(runner_manager) }
+
+ let_it_be(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let_it_be(:build_b) { create(:ci_build, :success) }
+
+ context 'with runner_manager of runner associated with build' do
+ let!(:runner_manager) { create(:ci_runner_machine, runner: runner) }
+ let!(:runner_manager_build) { create(:ci_runner_machine_build, build: build, runner_manager: runner_manager) }
+
+ it { is_expected.to contain_exactly(build) }
+ end
+
+ context 'with runner_manager of runner not associated with build' do
+ let!(:runner_manager) { create(:ci_runner_machine, runner: instance_runner) }
+ let!(:instance_runner) { create(:ci_runner, :with_runner_manager) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'with nil runner_manager' do
+ let(:runner_manager) { nil }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
describe '#stick_build_if_status_changed' do
it 'sticks the build if the status changed' do
job = create(:ci_build, :pending, pipeline: pipeline)
diff --git a/spec/models/ci/runner_manager_spec.rb b/spec/models/ci/runner_manager_spec.rb
index ca762f7d618..4fdfbae997d 100644
--- a/spec/models/ci/runner_manager_spec.rb
+++ b/spec/models/ci/runner_manager_spec.rb
@@ -122,23 +122,60 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
describe '.for_runner' do
subject(:runner_managers) { described_class.for_runner(runner_arg) }
- let_it_be(:runner1) { create(:ci_runner) }
- let_it_be(:runner_manager11) { create(:ci_runner_machine, runner: runner1) }
- let_it_be(:runner_manager12) { create(:ci_runner_machine, runner: runner1) }
+ let_it_be(:runner_a) { create(:ci_runner) }
+ let_it_be(:runner_manager_a1) { create(:ci_runner_machine, runner: runner_a) }
+ let_it_be(:runner_manager_a2) { create(:ci_runner_machine, runner: runner_a) }
context 'with single runner' do
- let(:runner_arg) { runner1 }
+ let(:runner_arg) { runner_a }
- it { is_expected.to contain_exactly(runner_manager11, runner_manager12) }
+ it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2) }
end
context 'with multiple runners' do
- let(:runner_arg) { [runner1, runner2] }
+ let(:runner_arg) { [runner_a, runner_b] }
- let_it_be(:runner2) { create(:ci_runner) }
- let_it_be(:runner_manager2) { create(:ci_runner_machine, runner: runner2) }
+ let_it_be(:runner_b) { create(:ci_runner) }
+ let_it_be(:runner_manager_b1) { create(:ci_runner_machine, runner: runner_b) }
- it { is_expected.to contain_exactly(runner_manager11, runner_manager12, runner_manager2) }
+ it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2, runner_manager_b1) }
+ end
+ end
+
+ describe '.with_system_xid' do
+ subject(:runner_managers) { described_class.with_system_xid(system_xid) }
+
+ let_it_be(:runner_a) { create(:ci_runner) }
+ let_it_be(:runner_b) { create(:ci_runner) }
+ let_it_be(:runner_manager_a1) { create(:ci_runner_machine, runner: runner_a, system_xid: 'id1') }
+ let_it_be(:runner_manager_a2) { create(:ci_runner_machine, runner: runner_a, system_xid: 'id2') }
+ let_it_be(:runner_manager_b1) { create(:ci_runner_machine, runner: runner_b, system_xid: 'id1') }
+
+ context 'with single system id' do
+ let(:system_xid) { 'id2' }
+
+ it { is_expected.to contain_exactly(runner_manager_a2) }
+ end
+
+ context 'with multiple system ids' do
+ let(:system_xid) { %w[id1 id2] }
+
+ it { is_expected.to contain_exactly(runner_manager_a1, runner_manager_a2, runner_manager_b1) }
+ end
+
+ context 'when chained with another scope' do
+ subject(:runner_managers) { described_class.for_runner(runner).with_system_xid(system_xid) }
+
+ let(:runner) { runner_a }
+ let(:system_xid) { 'id1' }
+
+ it { is_expected.to contain_exactly(runner_manager_a1) }
+
+ context 'with another runner' do
+ let(:runner) { runner_b }
+
+ it { is_expected.to contain_exactly(runner_manager_b1) }
+ end
end
end
@@ -146,18 +183,18 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
let!(:runner_version1) { create(:ci_runner_version, version: '16.0.0', status: :recommended) }
let!(:runner_version2) { create(:ci_runner_version, version: '16.0.1', status: :available) }
- let!(:runner1) { create(:ci_runner) }
- let!(:runner2) { create(:ci_runner) }
- let!(:runner_manager11) { create(:ci_runner_machine, runner: runner1, version: runner_version1.version) }
- let!(:runner_manager12) { create(:ci_runner_machine, runner: runner1, version: runner_version2.version) }
- let!(:runner_manager2) { create(:ci_runner_machine, runner: runner2, version: runner_version2.version) }
+ let!(:runner_a) { create(:ci_runner) }
+ let!(:runner_b) { create(:ci_runner) }
+ let!(:runner_manager_a1) { create(:ci_runner_machine, runner: runner_a, version: runner_version1.version) }
+ let!(:runner_manager_a2) { create(:ci_runner_machine, runner: runner_a, version: runner_version2.version) }
+ let!(:runner_manager_b1) { create(:ci_runner_machine, runner: runner_b, version: runner_version2.version) }
subject { described_class.aggregate_upgrade_status_by_runner_id }
it 'contains aggregate runner upgrade status by runner ID' do
is_expected.to eq({
- runner1.id => :recommended,
- runner2.id => :available
+ runner_a.id => :recommended,
+ runner_b.id => :available
})
end
end
@@ -189,6 +226,108 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
it { is_expected.to eq([runner_manager2, runner_manager1]) }
end
+ describe '.with_upgrade_status' do
+ subject(:scope) { described_class.with_upgrade_status(upgrade_status) }
+
+ let_it_be(:runner_manager_14_0_0) { create(:ci_runner_machine, version: '14.0.0') }
+ let_it_be(:runner_manager_14_1_0) { create(:ci_runner_machine, version: '14.1.0') }
+ let_it_be(:runner_manager_14_1_1) { create(:ci_runner_machine, version: '14.1.1') }
+
+ before_all do
+ create(:ci_runner_version, version: '14.0.0', status: :available)
+ create(:ci_runner_version, version: '14.1.0', status: :recommended)
+ create(:ci_runner_version, version: '14.1.1', status: :unavailable)
+ end
+
+ context 'as :unavailable' do
+ let(:upgrade_status) { :unavailable }
+
+ it 'returns runners with runner managers whose version is assigned :unavailable' do
+ is_expected.to contain_exactly(runner_manager_14_1_1)
+ end
+ end
+
+ context 'as :available' do
+ let(:upgrade_status) { :available }
+
+ it 'returns runners with runner managers whose version is assigned :available' do
+ is_expected.to contain_exactly(runner_manager_14_0_0)
+ end
+ end
+
+ context 'as :recommended' do
+ let(:upgrade_status) { :recommended }
+
+ it 'returns runners with runner managers whose version is assigned :recommended' do
+ is_expected.to contain_exactly(runner_manager_14_1_0)
+ end
+ end
+ end
+
+ describe '.with_version_prefix' do
+ subject { described_class.with_version_prefix(version_prefix) }
+
+ let_it_be(:runner_manager1) { create(:ci_runner_machine, version: '15.11.0') }
+ let_it_be(:runner_manager2) { create(:ci_runner_machine, version: '15.9.0') }
+ let_it_be(:runner_manager3) { create(:ci_runner_machine, version: '15.11.5') }
+
+ context 'with a prefix string of "15."' do
+ let(:version_prefix) { "15." }
+
+ it 'returns runner managers' do
+ is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
+ end
+ end
+
+ context 'with a prefix string of "15"' do
+ let(:version_prefix) { "15" }
+
+ it 'returns runner managers' do
+ is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
+ end
+ end
+
+ context 'with a prefix string of "15.11."' do
+ let(:version_prefix) { "15.11." }
+
+ it 'returns runner managers' do
+ is_expected.to contain_exactly(runner_manager1, runner_manager3)
+ end
+ end
+
+ context 'with a prefix string of "15.11"' do
+ let(:version_prefix) { "15.11" }
+
+ it 'returns runner managers' do
+ is_expected.to contain_exactly(runner_manager1, runner_manager3)
+ end
+ end
+
+ context 'with a prefix string of "15.9"' do
+ let(:version_prefix) { "15.9" }
+
+ it 'returns runner managers' do
+ is_expected.to contain_exactly(runner_manager2)
+ end
+ end
+
+ context 'with a prefix string of "15.11.5"' do
+ let(:version_prefix) { "15.11.5" }
+
+ it 'returns runner managers' do
+ is_expected.to contain_exactly(runner_manager3)
+ end
+ end
+
+ context 'with a malformed prefix of "V2"' do
+ let(:version_prefix) { "V2" }
+
+ it 'returns no runner managers' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '#status', :freeze_time do
let(:runner_manager) { build(:ci_runner_machine, created_at: 8.days.ago) }
@@ -425,106 +564,4 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
it { is_expected.to contain_exactly build }
end
end
-
- describe '.with_upgrade_status' do
- subject(:scope) { described_class.with_upgrade_status(upgrade_status) }
-
- let_it_be(:runner_manager_14_0_0) { create(:ci_runner_machine, version: '14.0.0') }
- let_it_be(:runner_manager_14_1_0) { create(:ci_runner_machine, version: '14.1.0') }
- let_it_be(:runner_manager_14_1_1) { create(:ci_runner_machine, version: '14.1.1') }
-
- before_all do
- create(:ci_runner_version, version: '14.0.0', status: :available)
- create(:ci_runner_version, version: '14.1.0', status: :recommended)
- create(:ci_runner_version, version: '14.1.1', status: :unavailable)
- end
-
- context 'as :unavailable' do
- let(:upgrade_status) { :unavailable }
-
- it 'returns runners with runner managers whose version is assigned :unavailable' do
- is_expected.to contain_exactly(runner_manager_14_1_1)
- end
- end
-
- context 'as :available' do
- let(:upgrade_status) { :available }
-
- it 'returns runners with runner managers whose version is assigned :available' do
- is_expected.to contain_exactly(runner_manager_14_0_0)
- end
- end
-
- context 'as :recommended' do
- let(:upgrade_status) { :recommended }
-
- it 'returns runners with runner managers whose version is assigned :recommended' do
- is_expected.to contain_exactly(runner_manager_14_1_0)
- end
- end
- end
-
- describe '.with_version_prefix' do
- subject { described_class.with_version_prefix(version_prefix) }
-
- let_it_be(:runner_manager1) { create(:ci_runner_machine, version: '15.11.0') }
- let_it_be(:runner_manager2) { create(:ci_runner_machine, version: '15.9.0') }
- let_it_be(:runner_manager3) { create(:ci_runner_machine, version: '15.11.5') }
-
- context 'with a prefix string of "15."' do
- let(:version_prefix) { "15." }
-
- it 'returns runner managers' do
- is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
- end
- end
-
- context 'with a prefix string of "15"' do
- let(:version_prefix) { "15" }
-
- it 'returns runner managers' do
- is_expected.to contain_exactly(runner_manager1, runner_manager2, runner_manager3)
- end
- end
-
- context 'with a prefix string of "15.11."' do
- let(:version_prefix) { "15.11." }
-
- it 'returns runner managers' do
- is_expected.to contain_exactly(runner_manager1, runner_manager3)
- end
- end
-
- context 'with a prefix string of "15.11"' do
- let(:version_prefix) { "15.11" }
-
- it 'returns runner managers' do
- is_expected.to contain_exactly(runner_manager1, runner_manager3)
- end
- end
-
- context 'with a prefix string of "15.9"' do
- let(:version_prefix) { "15.9" }
-
- it 'returns runner managers' do
- is_expected.to contain_exactly(runner_manager2)
- end
- end
-
- context 'with a prefix string of "15.11.5"' do
- let(:version_prefix) { "15.11.5" }
-
- it 'returns runner managers' do
- is_expected.to contain_exactly(runner_manager3)
- end
- end
-
- context 'with a malformed prefix of "V2"' do
- let(:version_prefix) { "V2" }
-
- it 'returns no runner managers' do
- is_expected.to be_empty
- end
- end
- end
end
diff --git a/spec/models/organizations/organization_detail_spec.rb b/spec/models/organizations/organization_detail_spec.rb
index 3f44a9cc637..dd49274e7dd 100644
--- a/spec/models/organizations/organization_detail_spec.rb
+++ b/spec/models/organizations/organization_detail_spec.rb
@@ -16,6 +16,15 @@ RSpec.describe Organizations::OrganizationDetail, type: :model, feature_category
let(:model) { create(:organization_detail) }
end
+ describe '#description_html' do
+ let_it_be(:model) { create(:organization_detail, description: '### Foo **Bar**') }
+ let(:expected_description) { ' Foo <strong>Bar</strong> ' }
+
+ subject { model.description_html }
+
+ it { is_expected.to eq_no_sourcepos(expected_description) }
+ end
+
context 'with uploads' do
it_behaves_like 'model with uploads', false do
let(:model_object) { create(:organization_detail) }
diff --git a/spec/models/organizations/organization_spec.rb b/spec/models/organizations/organization_spec.rb
index 053a32281aa..aba2b03d4d1 100644
--- a/spec/models/organizations/organization_spec.rb
+++ b/spec/models/organizations/organization_spec.rb
@@ -59,6 +59,7 @@ RSpec.describe Organizations::Organization, type: :model, feature_category: :cel
describe 'delegations' do
it { is_expected.to delegate_method(:description).to(:organization_detail) }
+ it { is_expected.to delegate_method(:description_html).to(:organization_detail) }
it { is_expected.to delegate_method(:avatar).to(:organization_detail) }
it { is_expected.to delegate_method(:avatar_url).to(:organization_detail) }
it { is_expected.to delegate_method(:remove_avatar!).to(:organization_detail) }
diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb
index b4394f47105..11d906249e4 100644
--- a/spec/requests/api/ci/runners_spec.rb
+++ b/spec/requests/api/ci/runners_spec.rb
@@ -836,166 +836,219 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :fleet_v
end
describe 'GET /runners/:id/jobs' do
- let_it_be(:job_1) { create(:ci_build) }
- let_it_be(:job_2) { create(:ci_build, :running, runner: shared_runner, project: project) }
- let_it_be(:job_3) { create(:ci_build, :failed, runner: shared_runner, project: project) }
- let_it_be(:job_4) { create(:ci_build, :running, runner: project_runner, project: project) }
- let_it_be(:job_5) { create(:ci_build, :failed, runner: project_runner, project: project) }
- let(:path) { "/runners/#{project_runner.id}/jobs" }
+ subject(:request) { get api(path, user, **api_params) }
+
+ let_it_be(:shared_runner_manager1) { create(:ci_runner_machine, runner: shared_runner, system_xid: 'id2') }
+ let_it_be(:jobs) do
+ project_runner_manager1 = create(:ci_runner_machine, runner: project_runner, system_xid: 'id1')
+ project_runner_manager2 = create(:ci_runner_machine, runner: two_projects_runner, system_xid: 'id1')
+
+ [
+ create(:ci_build),
+ create(:ci_build, :running, runner_manager: shared_runner_manager1, project: project),
+ create(:ci_build, :failed, runner_manager: shared_runner_manager1, project: project),
+ create(:ci_build, :running, runner_manager: project_runner_manager1, project: project),
+ create(:ci_build, :failed, runner_manager: project_runner_manager1, project: project),
+ create(:ci_build, :running, runner_manager: project_runner_manager2, project: project),
+ create(:ci_build, :running, runner_manager: project_runner_manager2, project: project2)
+ ]
+ end
+
+ let(:api_params) { {} }
+ let(:runner_id) { project_runner.id }
+ let(:query_part) { query_params.merge(system_id_params).map { |param| param.join('=') }.join('&') }
+ let(:path) { "/runners/#{runner_id}/jobs?#{query_part}" }
+ let(:query_params) { {} }
+ let(:system_id_params) { {} }
it_behaves_like 'GET request permissions for admin mode'
context 'admin user' do
+ let(:user) { admin }
+ let(:api_params) { { admin_mode: true } }
+
context 'when runner exists' do
context 'when runner is shared' do
+ let(:runner_id) { shared_runner.id }
+ let(:system_id) { 'id2' }
+
it 'return jobs' do
- get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
+ request
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(2)
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[1].id),
+ a_hash_including('id' => jobs[2].id)
+ ])
+ end
+
+ it_behaves_like 'an endpoint with keyset pagination', invalid_order: nil do
+ let(:first_record) { jobs[2] }
+ let(:second_record) { jobs[1] }
+ let(:api_call) { api(path, user, **api_params) }
end
end
context 'when runner is a project runner' do
it 'return jobs' do
- get api(path, admin, admin_mode: true)
+ request
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(2)
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[3].id),
+ a_hash_including('id' => jobs[4].id)
+ ])
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)
+ let(:runner_id) { two_projects_runner.id }
+ let(:user) { user2 }
+ let(:api_params) { {} }
+ before_all do
project.add_guest(user2)
project2.add_maintainer(user2)
- get api("/runners/#{two_projects_runner.id}/jobs", user2)
+ end
+
+ it 'shows only jobs it has permission to see' do
+ request
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)
+ expect(json_response).to match([a_hash_including('id' => jobs[6].id)])
end
end
end
context 'when valid status is provided' do
+ let(:query_params) { { status: :failed } }
+
it 'return filtered jobs' do
- get api("/runners/#{project_runner.id}/jobs?status=failed", admin, admin_mode: true)
+ request
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)
- expect(json_response.first).to include('id' => job_5.id)
+ expect(json_response).to match([a_hash_including('id' => jobs[4].id)])
end
end
context 'when valid order_by is provided' do
+ let(:query_params) { { order_by: :id } }
+
context 'when sort order is not specified' do
it 'return jobs in descending order' do
- get api("/runners/#{project_runner.id}/jobs?order_by=id", admin, admin_mode: true)
+ request
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(2)
- expect(json_response.first).to include('id' => job_5.id)
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[4].id),
+ a_hash_including('id' => jobs[3].id)
+ ])
end
end
context 'when sort order is specified as asc' do
+ let(:query_params) { { order_by: :id, sort: :asc } }
+
it 'return jobs sorted in ascending order' do
- get api("/runners/#{project_runner.id}/jobs?order_by=id&sort=asc", admin, admin_mode: true)
+ request
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(2)
- expect(json_response.first).to include('id' => job_4.id)
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[3].id),
+ a_hash_including('id' => jobs[4].id)
+ ])
end
end
end
context 'when invalid status is provided' do
+ let(:query_params) { { status: 'non-existing' } }
+
it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?status=non-existing", admin, admin_mode: true)
+ request
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when invalid order_by is provided' do
+ let(:query_params) { { order_by: 'non-existing' } }
+
it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?order_by=non-existing", admin, admin_mode: true)
+ request
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when invalid sort is provided' do
+ let(:query_params) { { sort: 'non-existing' } }
+
it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?sort=non-existing", admin, admin_mode: true)
+ request
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
- it 'avoids N+1 DB queries' do
- get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
+ describe 'eager loading' do
+ let(:runner_id) { shared_runner.id }
- control = ActiveRecord::QueryRecorder.new do
- get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
- end
+ it 'avoids N+1 DB queries' do
+ get api(path, user, **api_params)
- create(:ci_build, :failed, runner: shared_runner, project: project)
+ control = ActiveRecord::QueryRecorder.new do
+ get api(path, user, **api_params)
+ end
- expect do
- get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true)
- end.not_to exceed_query_limit(control)
- end
+ create(:ci_build, :failed, runner: shared_runner, project: project)
- it 'batches loading of commits' do
- shared_runner = create(:ci_runner, :instance, description: 'Shared runner')
+ expect do
+ get api(path, user, **api_params)
+ end.not_to exceed_query_limit(control.count)
+ end
- project_with_repo = create(:project, :repository)
+ it 'batches loading of commits' do
+ project_with_repo = create(:project, :repository)
+ shared_runner_manager1 = create(:ci_runner_machine, runner: shared_runner, system_xid: 'id1')
- pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'ddd0f15ae83993f5cb66a927a28673882e99100b')
- create(:ci_build, :running, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
+ pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'ddd0f15ae83993f5cb66a927a28673882e99100b')
+ create(:ci_build, :running, runner_manager: shared_runner_manager1, project: project_with_repo, pipeline: pipeline)
- pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'c1c67abbaf91f624347bb3ae96eabe3a1b742478')
- create(:ci_build, :failed, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
+ pipeline = create(:ci_pipeline, project: project_with_repo, sha: 'c1c67abbaf91f624347bb3ae96eabe3a1b742478')
+ create(:ci_build, :failed, runner_manager: shared_runner_manager1, project: project_with_repo, pipeline: pipeline)
- pipeline = create(:ci_pipeline, project: project_with_repo, sha: '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
- create(:ci_build, :failed, runner: shared_runner, project: project_with_repo, pipeline: pipeline)
+ pipeline = create(:ci_pipeline, project: project_with_repo, sha: '1a0b36b3cdad1d2ee32457c102a8c0b7056fa863')
+ create(:ci_build, :failed, runner_manager: shared_runner_manager1, project: project_with_repo, pipeline: pipeline)
- expect_next_instance_of(Repository) do |repo|
- expect(repo).to receive(:commits_by).with(oids:
- %w[
- 1a0b36b3cdad1d2ee32457c102a8c0b7056fa863
- c1c67abbaf91f624347bb3ae96eabe3a1b742478
- ]).once.and_call_original
- end
+ expect_next_instance_of(Repository) do |repo|
+ expect(repo).to receive(:commits_by).with(oids:
+ %w[
+ 1a0b36b3cdad1d2ee32457c102a8c0b7056fa863
+ c1c67abbaf91f624347bb3ae96eabe3a1b742478
+ ]).once.and_call_original
+ end
- get api("/runners/#{shared_runner.id}/jobs", admin, admin_mode: true), params: { per_page: 2, order_by: 'id', sort: 'desc' }
+ get api(path, admin, admin_mode: true), params: { per_page: 2, order_by: 'id', sort: 'desc' }
+ end
end
context "when runner doesn't exist" do
+ let(:runner_id) { non_existing_record_id }
+
it 'returns 404' do
- get api('/runners/0/jobs', admin, admin_mode: true)
+ request
expect(response).to have_gitlab_http_status(:not_found)
end
@@ -1004,70 +1057,118 @@ RSpec.describe API::Ci::Runners, :aggregate_failures, feature_category: :fleet_v
context "runner project's administrative user" do
context 'when runner exists' do
+ let(:runner_id) { shared_runner.id }
+
context 'when runner is shared' do
it 'returns 403' do
- get api("/runners/#{shared_runner.id}/jobs", user)
+ request
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when runner is a project runner' do
+ let(:runner_id) { project_runner.id }
+
it 'return jobs' do
- get api(path, user)
+ request
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(2)
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[3].id),
+ a_hash_including('id' => jobs[4].id)
+ ])
end
- end
- context 'when valid status is provided' do
- it 'return filtered jobs' do
- get api("/runners/#{project_runner.id}/jobs?status=failed", user)
+ context 'when valid status is provided' do
+ let(:query_params) { { status: :failed } }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
+ it 'return filtered jobs' do
+ request
+
+ 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)
- expect(json_response.first).to include('id' => job_5.id)
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[4].id)
+ ])
+ end
end
- end
- context 'when invalid status is provided' do
- it 'return 400' do
- get api("/runners/#{project_runner.id}/jobs?status=non-existing", user)
+ context 'when invalid status is provided' do
+ let(:query_params) { { status: 'non-existing' } }
- expect(response).to have_gitlab_http_status(:bad_request)
+ it 'return 400' do
+ request
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
end
end
end
context "when runner doesn't exist" do
+ let(:runner_id) { non_existing_record_id }
+
it 'returns 404' do
- get api('/runners/0/jobs', user)
+ request
expect(response).to have_gitlab_http_status(:not_found)
end
end
- end
- context 'other authorized user' do
- it 'does not return jobs' do
- get api(path, user2)
+ context 'other authorized user' do
+ let(:user) { user2 }
- expect(response).to have_gitlab_http_status(:forbidden)
+ it 'does not return jobs' do
+ request
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:user) { nil }
+
+ it 'does not return jobs' do
+ request
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
end
end
- context 'unauthorized user' do
- it 'does not return jobs' do
- get api(path)
+ context 'with system_id param' do
+ let(:system_id_params) { { system_id: system_id } }
+ let(:system_id) { 'id1' }
+ let(:user) { admin }
+ let(:api_params) { { admin_mode: true } }
- expect(response).to have_gitlab_http_status(:unauthorized)
+ it 'returns jobs from the runner manager' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_limited_pagination_headers
+ expect(response.headers).not_to include('X-Total', 'X-Total-Pages')
+
+ expect(json_response).to match([
+ a_hash_including('id' => jobs[3].id),
+ a_hash_including('id' => jobs[4].id)
+ ])
+ end
+
+ context 'when system_id does not match runner' do
+ let(:runner_id) { shared_runner.id }
+
+ it 'does not return jobs' do
+ request
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(json_response).to be_empty
+ end
end
end
end
diff --git a/spec/requests/organizations/organizations_controller_spec.rb b/spec/requests/organizations/organizations_controller_spec.rb
index bfd0603eb3d..381e7f98dbe 100644
--- a/spec/requests/organizations/organizations_controller_spec.rb
+++ b/spec/requests/organizations/organizations_controller_spec.rb
@@ -119,4 +119,28 @@ RSpec.describe Organizations::OrganizationsController, feature_category: :cell d
it_behaves_like 'controller action that requires authentication by any user'
end
+
+ describe 'POST #preview_markdown' do
+ subject(:gitlab_request) { post preview_markdown_organizations_path, params: { text: '### Foo \n **bar**' } }
+
+ it_behaves_like 'controller action that requires authentication by any user'
+
+ context 'when the user is signed in' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'returns html from markdown' do
+ sign_in(user)
+ gitlab_request
+
+ body = Gitlab::Json.parse(response.body)['body']
+
+ expect(body).not_to include('Foo</h3>')
+ expect(body).to include('<strong>bar</strong>')
+ end
+ end
+ end
end
diff --git a/spec/routing/organizations/organizations_controller_routing_spec.rb b/spec/routing/organizations/organizations_controller_routing_spec.rb
index f105bb31ccf..c5c7a0ae377 100644
--- a/spec/routing/organizations/organizations_controller_routing_spec.rb
+++ b/spec/routing/organizations/organizations_controller_routing_spec.rb
@@ -29,4 +29,9 @@ RSpec.describe Organizations::OrganizationsController, :routing, feature_categor
expect(get("/-/organizations/#{organization.path}/users"))
.to route_to('organizations/organizations#users', organization_path: organization.path)
end
+
+ it 'routes to #preview_markdown' do
+ expect(post("/-/organizations/preview_markdown"))
+ .to route_to('organizations/organizations#preview_markdown')
+ end
end
diff --git a/spec/rubocop/cop/scalability/file_uploads_spec.rb b/spec/rubocop/cop/scalability/file_uploads_spec.rb
index 43ac9457ed6..50049d76f0e 100644
--- a/spec/rubocop/cop/scalability/file_uploads_spec.rb
+++ b/spec/rubocop/cop/scalability/file_uploads_spec.rb
@@ -4,7 +4,7 @@ require 'rubocop_spec_helper'
require_relative '../../../../rubocop/cop/scalability/file_uploads'
RSpec.describe RuboCop::Cop::Scalability::FileUploads, feature_category: :scalability do
- let(:message) { 'Do not upload files without workhorse acceleration. Please refer to https://docs.gitlab.com/ee/development/uploads.html' }
+ let(:message) { 'Do not upload files without workhorse acceleration. Please refer to https://docs.gitlab.com/ee/development/uploads/' }
context 'with required params' do
it 'detects File in types array' do
diff --git a/spec/support/helpers/debug_with_puts.rb b/spec/support/helpers/debug_with_puts.rb
new file mode 100644
index 00000000000..b8599cc7d40
--- /dev/null
+++ b/spec/support/helpers/debug_with_puts.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+# TODO: Remove the debug_with_puts statements below! Used for debugging purposes.
+# TODO: https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/323#note_1688925316
+module DebugWithPuts
+ def debug_with_puts(message)
+ return unless ENV['CI'] # rubocop:disable RSpec/AvoidConditionalStatements -- Debug information only in the CI
+
+ warn "[#{Time.current}] #{message}"
+ end
+
+ module_function :debug_with_puts
+end
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
index bde5535705e..b77b366e037 100644
--- a/spec/support/helpers/stub_requests.rb
+++ b/spec/support/helpers/stub_requests.rb
@@ -18,15 +18,15 @@ module StubRequests
end
def stub_dns(url, ip_address:, port: 80)
- debug_with_puts "beginning of stub_dns"
+ DebugWithPuts.debug_with_puts "beginning of stub_dns"
url = parse_url(url)
- debug_with_puts "before socket = Socket.sockaddr_in"
+ DebugWithPuts.debug_with_puts "before socket = Socket.sockaddr_in"
socket = Socket.sockaddr_in(port, ip_address)
- debug_with_puts "after socket = Socket.sockaddr_in"
+ DebugWithPuts.debug_with_puts "after socket = Socket.sockaddr_in"
- debug_with_puts "before addr = Addrinfo.new(socket)"
+ DebugWithPuts.debug_with_puts "before addr = Addrinfo.new(socket)"
addr = Addrinfo.new(socket)
- debug_with_puts "after addr = Addrinfo.new(socket)"
+ DebugWithPuts.debug_with_puts "after addr = Addrinfo.new(socket)"
# See Gitlab::UrlBlocker
allow(Addrinfo).to receive(:getaddrinfo)
@@ -58,12 +58,4 @@ module StubRequests
def parse_url(url)
url.is_a?(URI) ? url : URI(url)
end
-
- # TODO: Remove the debug_with_puts statements below! Used for debugging purposes.
- # TODO: https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/issues/323#note_1688925316
- def debug_with_puts(message)
- return unless ENV['CI'] # rubocop:disable RSpec/AvoidConditionalStatements -- Debug information only in the CI
-
- puts "[#{Time.current}] #{message}"
- end
end