diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-05 21:21:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2024-01-05 21:21:08 +0300 |
commit | 534ce3b2d0a6ec24de9c370e5b85c9528ff63e34 (patch) | |
tree | 4ad964818b181fddc0925e33b63f9b1f2ded23d3 /spec | |
parent | 4ba8ae97071935c39216afc53304c60386bbfa68 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 |