diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-29 21:08:35 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-29 21:08:35 +0000 |
commit | d8b32df644a632b143d6b9967311301a2fc83a6b (patch) | |
tree | 130722547715d1f65104529d8a09b1ba123776d6 /spec | |
parent | b64a8161c9442d82897a341d6bf935dd3e748b06 (diff) | |
download | gitlab-ce-d8b32df644a632b143d6b9967311301a2fc83a6b.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
28 files changed, 747 insertions, 55 deletions
diff --git a/spec/controllers/projects/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb index 4ae29ba7f54..91a29833314 100644 --- a/spec/controllers/projects/badges_controller_spec.rb +++ b/spec/controllers/projects/badges_controller_spec.rb @@ -54,7 +54,7 @@ describe Projects::BadgesController do context 'when style param is set to `flat`' do it 'renders the `flat` badge layout' do - get_badge(badge_type, 'flat') + get_badge(badge_type, style: 'flat') expect(response).to render_template('projects/badges/badge') end @@ -62,7 +62,7 @@ describe Projects::BadgesController do context 'when style param is set to an invalid type' do it 'renders the `flat` (default) badge layout' do - get_badge(badge_type, 'xxx') + get_badge(badge_type, style: 'xxx') expect(response).to render_template('projects/badges/badge') end @@ -70,7 +70,7 @@ describe Projects::BadgesController do context 'when style param is set to `flat-square`' do it 'renders the `flat-square` badge layout' do - get_badge(badge_type, 'flat-square') + get_badge(badge_type, style: 'flat-square') expect(response).to render_template('projects/badges/badge_flat-square') end @@ -102,12 +102,37 @@ describe Projects::BadgesController do end it 'defaults to project permissions' do - get_badge(:coverage) + get_badge(badge_type) expect(response).to have_gitlab_http_status(:not_found) end end end + + context 'customization' do + render_views + + before do + project.add_maintainer(user) + sign_in(user) + end + + context 'when key_text param is used' do + it 'sets custom key text' do + get_badge(badge_type, key_text: 'custom key text') + + expect(response.body).to include('custom key text') + end + end + + context 'when key_width param is used' do + it 'sets custom key width' do + get_badge(badge_type, key_width: '123') + + expect(response.body).to include('123') + end + end + end end describe '#pipeline' do @@ -118,13 +143,12 @@ describe Projects::BadgesController do it_behaves_like 'a badge resource', :coverage end - def get_badge(badge, style = nil) + def get_badge(badge, args = {}) params = { namespace_id: project.namespace.to_param, project_id: project, - ref: pipeline.ref, - style: style - } + ref: pipeline.ref + }.merge(args.slice(:style, :key_text, :key_width)) get badge, params: params, format: :svg end diff --git a/spec/factories/alert_management/alerts.rb b/spec/factories/alert_management/alerts.rb index a23d04dcbe0..8724a626d77 100644 --- a/spec/factories/alert_management/alerts.rb +++ b/spec/factories/alert_management/alerts.rb @@ -19,6 +19,12 @@ FactoryBot.define do issue end + trait :with_assignee do |alert| + after(:create) do |alert| + alert.alert_assignees.create(assignee: create(:user)) + end + end + trait :with_fingerprint do fingerprint { SecureRandom.hex } end @@ -77,6 +83,7 @@ FactoryBot.define do trait :all_fields do with_issue + with_assignee with_fingerprint with_service with_monitoring_tool diff --git a/spec/factories/ci/build_report_results.rb b/spec/factories/ci/build_report_results.rb new file mode 100644 index 00000000000..00009ead126 --- /dev/null +++ b/spec/factories/ci/build_report_results.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :ci_build_report_result, class: 'Ci::BuildReportResult' do + build factory: :ci_build + project factory: :project + data do + { + junit: { + name: "rspec", + duration: 0.42, + failed: 0, + errored: 2, + skipped: 0, + success: 0 + } + } + end + + trait :with_junit_success do + data do + { + junit: { + name: "rspec", + duration: 0.42, + failed: 0, + errored: 0, + skipped: 0, + success: 2 + } + } + end + end + end +end diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_list_spec.js index 2b135b02541..8d7fbbb763f 100644 --- a/spec/frontend/alert_management/components/alert_management_list_spec.js +++ b/spec/frontend/alert_management/components/alert_management_list_spec.js @@ -8,7 +8,7 @@ import { GlDropdownItem, GlIcon, GlTab, - GlDeprecatedBadge as GlBadge, + GlBadge, } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -42,6 +42,7 @@ describe('AlertManagementList', () => { const findStatusFilterBadge = () => wrapper.findAll(GlBadge); const findDateFields = () => wrapper.findAll(TimeAgo); const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem); + const findAssignees = () => wrapper.findAll('[data-testid="assigneesField"]'); const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]'); const findSeverityColumnHeader = () => wrapper.findAll('th').at(0); @@ -235,6 +236,34 @@ describe('AlertManagementList', () => { ).toBe('Critical'); }); + it('renders Unassigned when no assignee(s) present', () => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: mockAlerts, alertsCount, errored: false }, + loading: false, + }); + + expect( + findAssignees() + .at(0) + .text(), + ).toBe('Unassigned'); + }); + + it('renders username(s) when assignee(s) present', () => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: mockAlerts, alertsCount, errored: false }, + loading: false, + }); + + expect( + findAssignees() + .at(1) + .text(), + ).toBe(mockAlerts[1].assignees[0].username); + }); + it('navigates to the detail page when alert row is clicked', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/alert_management/mocks/alerts.json index 1615bb433d5..402adc4675b 100644 --- a/spec/frontend/alert_management/mocks/alerts.json +++ b/spec/frontend/alert_management/mocks/alerts.json @@ -7,7 +7,8 @@ "createdAt": "2020-04-17T23:18:14.996Z", "startedAt": "2020-04-17T23:18:14.996Z", "endedAt": "2020-04-17T23:18:14.996Z", - "status": "TRIGGERED" + "status": "TRIGGERED", + "assignees": [] }, { "iid": "1527543", @@ -16,7 +17,8 @@ "eventCount": 1, "startedAt": "2020-04-17T23:18:14.996Z", "endedAt": "2020-04-17T23:18:14.996Z", - "status": "ACKNOWLEDGED" + "status": "ACKNOWLEDGED", + "assignees": [{"username": "root"}] }, { "iid": "1527544", @@ -25,6 +27,7 @@ "eventCount": 4, "startedAt": "2020-04-17T23:18:14.996Z", "endedAt": "2020-04-17T23:18:14.996Z", - "status": "RESOLVED" + "status": "RESOLVED", + "assignees": [{"username": "root"}] } ] diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap index 9f269270186..4b08163f30a 100644 --- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap @@ -132,6 +132,8 @@ exports[`Dashboard template matches the default snapshot 1`] = ` <!----> + <!----> + <empty-state-stub clusterspath="/path/to/clusters" documentationpath="/path/to/docs" diff --git a/spec/frontend/monitoring/components/links_section_spec.js b/spec/frontend/monitoring/components/links_section_spec.js new file mode 100644 index 00000000000..3b5b72d84ee --- /dev/null +++ b/spec/frontend/monitoring/components/links_section_spec.js @@ -0,0 +1,64 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import { createStore } from '~/monitoring/stores'; +import LinksSection from '~/monitoring/components/links_section.vue'; + +describe('Links Section component', () => { + let store; + let wrapper; + + const createShallowWrapper = () => { + wrapper = shallowMount(LinksSection, { + store, + }); + }; + const setState = links => { + store.state.monitoringDashboard = { + ...store.state.monitoringDashboard, + showEmptyState: false, + links, + }; + }; + const findLinks = () => wrapper.findAll(GlLink); + + beforeEach(() => { + store = createStore(); + createShallowWrapper(); + }); + + it('does not render a section if no links are present', () => { + setState(); + + return wrapper.vm.$nextTick(() => { + expect(findLinks()).not.toExist(); + }); + }); + + it('renders a link inside a section', () => { + setState([ + { + title: 'GitLab Website', + url: 'https://gitlab.com', + }, + ]); + + return wrapper.vm.$nextTick(() => { + expect(findLinks()).toHaveLength(1); + const firstLink = findLinks().at(0); + + expect(firstLink.attributes('href')).toBe('https://gitlab.com'); + expect(firstLink.text()).toBe('GitLab Website'); + }); + }); + + it('renders multiple links inside a section', () => { + const links = new Array(10) + .fill(null) + .map((_, i) => ({ title: `Title ${i}`, url: `https://gitlab.com/projects/${i}` })); + setState(links); + + return wrapper.vm.$nextTick(() => { + expect(findLinks()).toHaveLength(10); + }); + }); +}); diff --git a/spec/graphql/types/alert_management/alert_type_spec.rb b/spec/graphql/types/alert_management/alert_type_spec.rb index 9c326f30e3c..1ef2e63f47e 100644 --- a/spec/graphql/types/alert_management/alert_type_spec.rb +++ b/spec/graphql/types/alert_management/alert_type_spec.rb @@ -24,6 +24,7 @@ describe GitlabSchema.types['AlertManagementAlert'] do details created_at updated_at + assignees ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb index 0fc9d3c1e9e..250e2f16aec 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb @@ -6,7 +6,8 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do let_it_be(:project) { create(:project, :empty_repo) } let_it_be(:mr1) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 3.months.ago) } let_it_be(:mr2) { create(:merge_request, target_project: project, source_project: project, allow_broken: true, created_at: 1.month.ago) } - let(:params) { {} } + let_it_be(:user) { create(:user) } + let(:params) { { current_user: user } } let(:records) do stage = build(:cycle_analytics_project_stage, { start_event_identifier: :merge_request_created, @@ -17,6 +18,7 @@ describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do end before do + project.add_maintainer(user) mr1.metrics.update!(merged_at: 1.month.ago) mr2.metrics.update!(merged_at: Time.now) end diff --git a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb index 334cab0b799..e3429b0ca57 100644 --- a/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb +++ b/spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do describe '#serialized_records' do shared_context 'when records are loaded by maintainer' do before do - project.add_user(user, Gitlab::Access::MAINTAINER) + project.add_user(user, Gitlab::Access::DEVELOPER) end it 'returns all records' do @@ -103,6 +103,8 @@ describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do latest_build_finished_at: 7.days.ago, pipeline: ci_build2.pipeline }) + + project.add_user(user, Gitlab::Access::MAINTAINER) end context 'returns build records' do diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb index 560072a3d83..284ca53a996 100644 --- a/spec/lib/gitlab/badge/coverage/report_spec.rb +++ b/spec/lib/gitlab/badge/coverage/report_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::Badge::Coverage::Report do let(:job_name) { nil } let(:badge) do - described_class.new(project, 'master', job_name) + described_class.new(project, 'master', opts: { job: job_name }) end describe '#entity' do diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb index b51d707a61d..3940b37830e 100644 --- a/spec/lib/gitlab/badge/coverage/template_spec.rb +++ b/spec/lib/gitlab/badge/coverage/template_spec.rb @@ -3,13 +3,33 @@ require 'spec_helper' describe Gitlab::Badge::Coverage::Template do - let(:badge) { double(entity: 'coverage', status: 90.00) } + let(:badge) { double(entity: 'coverage', status: 90.00, customization: {}) } let(:template) { described_class.new(badge) } describe '#key_text' do - it 'is always says coverage' do + it 'says coverage by default' do expect(template.key_text).to eq 'coverage' end + + context 'when custom key_text is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_text: "custom text" }) + end + + it 'returns custom value' do + expect(template.key_text).to eq "custom text" + end + + context 'when its size is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_text: 't' * 129 }) + end + + it 'returns default value' do + expect(template.key_text).to eq 'coverage' + end + end + end end describe '#value_text' do @@ -41,9 +61,29 @@ describe Gitlab::Badge::Coverage::Template do end describe '#key_width' do - it 'has a fixed key width' do + it 'is fixed by default' do expect(template.key_width).to eq 62 end + + context 'when custom key_width is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_width: 101 }) + end + + it 'returns custom value' do + expect(template.key_width).to eq 101 + end + + context 'when it is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_width: 129 }) + end + + it 'returns default value' do + expect(template.key_width).to eq 62 + end + end + end end describe '#value_width' do diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb index da95c7219a4..751a5d6645e 100644 --- a/spec/lib/gitlab/badge/pipeline/template_spec.rb +++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb @@ -3,13 +3,33 @@ require 'spec_helper' describe Gitlab::Badge::Pipeline::Template do - let(:badge) { double(entity: 'pipeline', status: 'success') } + let(:badge) { double(entity: 'pipeline', status: 'success', customization: {}) } let(:template) { described_class.new(badge) } describe '#key_text' do - it 'is always says pipeline' do + it 'says pipeline by default' do expect(template.key_text).to eq 'pipeline' end + + context 'when custom key_text is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_text: 'custom text' }) + end + + it 'returns custom value' do + expect(template.key_text).to eq 'custom text' + end + + context 'when its size is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_text: 't' * 129 }) + end + + it 'returns default value' do + expect(template.key_text).to eq 'pipeline' + end + end + end end describe '#value_text' do @@ -18,6 +38,32 @@ describe Gitlab::Badge::Pipeline::Template do end end + describe '#key_width' do + it 'is fixed by default' do + expect(template.key_width).to eq 62 + end + + context 'when custom key_width is defined' do + before do + allow(badge).to receive(:customization).and_return({ key_width: 101 }) + end + + it 'returns custom value' do + expect(template.key_width).to eq 101 + end + + context 'when it is larger than the max allowed value' do + before do + allow(badge).to receive(:customization).and_return({ key_width: 129 }) + end + + it 'returns default value' do + expect(template.key_width).to eq 62 + end + end + end + end + describe 'widths and text anchors' do it 'has fixed width and text anchors' do expect(template.width).to eq 116 diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb index 0e2fb047469..0741344a666 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb @@ -2,17 +2,19 @@ require 'spec_helper' -describe Gitlab::Database::PartitioningMigrationHelpers do +describe Gitlab::Database::PartitioningMigrationHelpers::ForeignKeyHelpers do let(:model) do ActiveRecord::Migration.new.extend(described_class) end let_it_be(:connection) { ActiveRecord::Base.connection } let(:referenced_table) { :issues } - let(:function_name) { model.fk_function_name(referenced_table) } - let(:trigger_name) { model.fk_trigger_name(referenced_table) } + let(:function_name) { '_test_partitioned_foreign_keys_function' } + let(:trigger_name) { '_test_partitioned_foreign_keys_trigger' } before do allow(model).to receive(:puts) + allow(model).to receive(:fk_function_name).and_return(function_name) + allow(model).to receive(:fk_trigger_name).and_return(trigger_name) end describe 'adding a foreign key' do diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb new file mode 100644 index 00000000000..3a164e35323 --- /dev/null +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers do + include PartitioningHelpers + + let(:model) do + ActiveRecord::Migration.new.extend(described_class) + end + + let_it_be(:connection) { ActiveRecord::Base.connection } + let(:template_table) { :audit_events } + let(:partitioned_table) { '_test_migration_partitioned_table' } + let(:partition_column) { 'created_at' } + let(:min_date) { Date.new(2019, 12) } + let(:max_date) { Date.new(2020, 3) } + + before do + allow(model).to receive(:puts) + allow(model).to receive(:partitioned_table_name).and_return(partitioned_table) + end + + describe '#partition_table_by_date' do + let(:old_primary_key) { 'id' } + let(:new_primary_key) { [old_primary_key, partition_column] } + + context 'when the the max_date is less than the min_date' do + let(:max_date) { Time.utc(2019, 6) } + + it 'raises an error' do + expect do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + end.to raise_error(/max_date #{max_date} must be greater than min_date #{min_date}/) + end + end + + context 'when the max_date is equal to the min_date' do + let(:max_date) { min_date } + + it 'raises an error' do + expect do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + end.to raise_error(/max_date #{max_date} must be greater than min_date #{min_date}/) + end + end + + context 'when the given table does not have a primary key' do + let(:template_table) { :_partitioning_migration_helper_test_table } + let(:partition_column) { :some_field } + + it 'raises an error' do + model.create_table template_table, id: false do |t| + t.integer :id + t.datetime partition_column + end + + expect do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + end.to raise_error(/primary key not defined for #{template_table}/) + end + end + + context 'when an invalid partition column is given' do + let(:partition_column) { :_this_is_not_real } + + it 'raises an error' do + expect do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + end.to raise_error(/partition column #{partition_column} does not exist/) + end + end + + context 'when a valid source table and partition column is given' do + it 'creates a table partitioned by the proper column' do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + + expect(connection.table_exists?(partitioned_table)).to be(true) + expect(connection.primary_key(partitioned_table)).to eq(new_primary_key) + + expect_table_partitioned_by(partitioned_table, [partition_column]) + end + + it 'removes the default from the primary key column' do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + + pk_column = connection.columns(partitioned_table).find { |c| c.name == old_primary_key } + + expect(pk_column.default_function).to be_nil + end + + it 'creates the partitioned table with the same non-key columns' do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + + copied_columns = filter_columns_by_name(connection.columns(partitioned_table), new_primary_key) + original_columns = filter_columns_by_name(connection.columns(template_table), new_primary_key) + + expect(copied_columns).to match_array(original_columns) + end + + it 'creates a partition spanning over each month in the range given' do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + + expect_range_partition_of("#{partitioned_table}_000000", partitioned_table, 'MINVALUE', "'2019-12-01 00:00:00'") + expect_range_partition_of("#{partitioned_table}_201912", partitioned_table, "'2019-12-01 00:00:00'", "'2020-01-01 00:00:00'") + expect_range_partition_of("#{partitioned_table}_202001", partitioned_table, "'2020-01-01 00:00:00'", "'2020-02-01 00:00:00'") + expect_range_partition_of("#{partitioned_table}_202002", partitioned_table, "'2020-02-01 00:00:00'", "'2020-03-01 00:00:00'") + end + end + end + + describe '#drop_partitioned_table_for' do + let(:expected_tables) do + %w[000000 201912 202001 202002].map { |suffix| "#{partitioned_table}_#{suffix}" }.unshift(partitioned_table) + end + + it 'drops the partitioned copy and all partitions' do + model.partition_table_by_date template_table, partition_column, min_date: min_date, max_date: max_date + + expected_tables.each do |table| + expect(connection.table_exists?(table)).to be(true) + end + + model.drop_partitioned_table_for template_table + + expected_tables.each do |table| + expect(connection.table_exists?(table)).to be(false) + end + end + end + + def filter_columns_by_name(columns, names) + columns.reject { |c| names.include?(c.name) } + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 081bf36c3f3..8033e80010d 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -495,6 +495,7 @@ project: - repository_storage_moves - freeze_periods - webex_teams_service +- build_report_results award_emoji: - awardable - user diff --git a/spec/models/alert_management/alert_assignee_spec.rb b/spec/models/alert_management/alert_assignee_spec.rb new file mode 100644 index 00000000000..c51a5d543ab --- /dev/null +++ b/spec/models/alert_management/alert_assignee_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe AlertManagement::AlertAssignee do + describe 'associations' do + it { is_expected.to belong_to(:alert) } + it { is_expected.to belong_to(:assignee) } + end + + describe 'validations' do + let(:alert) { create(:alert_management_alert) } + let(:user) { create(:user) } + + subject { alert.alert_assignees.build(assignee: user) } + + it { is_expected.to validate_presence_of(:alert) } + it { is_expected.to validate_presence_of(:assignee) } + it { is_expected.to validate_uniqueness_of(:assignee).scoped_to(:alert_id) } + end +end diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb index af9f890da79..ea58f2f4337 100644 --- a/spec/models/alert_management/alert_spec.rb +++ b/spec/models/alert_management/alert_spec.rb @@ -6,6 +6,7 @@ describe AlertManagement::Alert do describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:issue) } + it { is_expected.to have_many(:assignees).through(:alert_assignees) } end describe 'validations' do diff --git a/spec/models/ci/build_report_result_spec.rb b/spec/models/ci/build_report_result_spec.rb new file mode 100644 index 00000000000..e9211a22d08 --- /dev/null +++ b/spec/models/ci/build_report_result_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Ci::BuildReportResult do + let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) } + + describe 'associations' do + it { is_expected.to belong_to(:build) } + it { is_expected.to belong_to(:project) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:build) } + + context 'when attributes are valid' do + it 'returns no errors' do + expect(build_report_result).to be_valid + end + end + + context 'when data is invalid' do + it 'returns errors' do + build_report_result.data = { invalid: 'data' } + + expect(build_report_result).to be_invalid + expect(build_report_result.errors.full_messages).to eq(["Data must be a valid json schema"]) + end + end + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index ddf12f68160..e17471e4527 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -24,6 +24,7 @@ describe Ci::Build do it { is_expected.to have_many(:needs) } it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:job_variables) } + it { is_expected.to have_many(:report_results) } it { is_expected.to have_one(:deployment) } it { is_expected.to have_one(:runner_session) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2b98ae492ba..6c988719c1a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -79,6 +79,7 @@ describe Project do it { is_expected.to have_many(:ci_refs) } it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:build_trace_section_names)} + it { is_expected.to have_many(:build_report_results) } it { is_expected.to have_many(:runner_projects) } it { is_expected.to have_many(:runners) } it { is_expected.to have_many(:variables) } diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb index c226e659364..f6f80b55d93 100644 --- a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb @@ -75,6 +75,8 @@ describe 'getting Alert Management Alerts' do 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ') ) + expect(first_alert['assignees'].first).to include('username' => triggered_alert.assignees.first.username) + expect(second_alert).to include( 'iid' => resolved_alert.iid.to_s, 'issueIid' => nil, diff --git a/spec/requests/api/project_repository_storage_moves_spec.rb b/spec/requests/api/project_repository_storage_moves_spec.rb index 7ceea0178f3..8d361cf3fd9 100644 --- a/spec/requests/api/project_repository_storage_moves_spec.rb +++ b/spec/requests/api/project_repository_storage_moves_spec.rb @@ -5,12 +5,45 @@ require 'spec_helper' describe API::ProjectRepositoryStorageMoves do include AccessMatchersForRequest - let(:user) { create(:admin) } - let!(:storage_move) { create(:project_repository_storage_move, :scheduled) } + let_it_be(:user) { create(:admin) } + let_it_be(:project) { create(:project) } + let_it_be(:storage_move) { create(:project_repository_storage_move, :scheduled, project: project) } - describe 'GET /project_repository_storage_moves' do + shared_examples 'get single project repository storage move' do + let(:project_repository_storage_move_id) { storage_move.id } + + def get_project_repository_storage_move + get api(url, user) + end + + it 'returns a project repository storage move' do + get_project_repository_storage_move + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/project_repository_storage_move') + expect(json_response['id']).to eq(storage_move.id) + expect(json_response['state']).to eq(storage_move.human_state_name) + end + + context 'non-existent project repository storage move' do + let(:project_repository_storage_move_id) { non_existing_record_id } + + it 'returns not found' do + get_project_repository_storage_move + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe 'permissions' do + it { expect { get_project_repository_storage_move }.to be_allowed_for(:admin) } + it { expect { get_project_repository_storage_move }.to be_denied_for(:user) } + end + end + + shared_examples 'get project repository storage move list' do def get_project_repository_storage_moves - get api('/project_repository_storage_moves', user) + get api(url, user) end it 'returns project repository storage moves' do @@ -30,16 +63,16 @@ describe API::ProjectRepositoryStorageMoves do control = ActiveRecord::QueryRecorder.new { get_project_repository_storage_moves } - create(:project_repository_storage_move, :scheduled) + create(:project_repository_storage_move, :scheduled, project: project) expect { get_project_repository_storage_moves }.not_to exceed_query_limit(control) end it 'returns the most recently created first' do - storage_move_oldest = create(:project_repository_storage_move, :scheduled, created_at: 2.days.ago) - storage_move_middle = create(:project_repository_storage_move, :scheduled, created_at: 1.day.ago) + storage_move_oldest = create(:project_repository_storage_move, :scheduled, project: project, created_at: 2.days.ago) + storage_move_middle = create(:project_repository_storage_move, :scheduled, project: project, created_at: 1.day.ago) - get api('/project_repository_storage_moves', user) + get_project_repository_storage_moves json_ids = json_response.map {|storage_move| storage_move['id'] } expect(json_ids).to eq([ @@ -55,35 +88,27 @@ describe API::ProjectRepositoryStorageMoves do end end - describe 'GET /project_repository_storage_moves/:id' do - let(:project_repository_storage_move_id) { storage_move.id } - - def get_project_repository_storage_move - get api("/project_repository_storage_moves/#{project_repository_storage_move_id}", user) + describe 'GET /project_repository_storage_moves' do + it_behaves_like 'get project repository storage move list' do + let(:url) { '/project_repository_storage_moves' } end + end - it 'returns a project repository storage move' do - get_project_repository_storage_move - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('public_api/v4/project_repository_storage_move') - expect(json_response['id']).to eq(storage_move.id) - expect(json_response['state']).to eq(storage_move.human_state_name) + describe 'GET /project_repository_storage_moves/:repository_storage_move_id' do + it_behaves_like 'get single project repository storage move' do + let(:url) { "/project_repository_storage_moves/#{project_repository_storage_move_id}" } end + end - context 'non-existent project repository storage move' do - let(:project_repository_storage_move_id) { non_existing_record_id } - - it 'returns not found' do - get_project_repository_storage_move - - expect(response).to have_gitlab_http_status(:not_found) - end + describe 'GET /projects/:id/repository_storage_moves' do + it_behaves_like 'get project repository storage move list' do + let(:url) { "/projects/#{project.id}/repository_storage_moves" } end + end - describe 'permissions' do - it { expect { get_project_repository_storage_move }.to be_allowed_for(:admin) } - it { expect { get_project_repository_storage_move }.to be_denied_for(:user) } + describe 'GET /projects/:id/repository_storage_moves/:repository_storage_move_id' do + it_behaves_like 'get single project repository storage move' do + let(:url) { "/projects/#{project.id}/repository_storage_moves/#{project_repository_storage_move_id}" } end end end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 0aa603b24ae..64e3cda7c91 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -49,7 +49,7 @@ describe Ci::RetryBuildService do metadata runner_session trace_chunks upstream_pipeline_id artifacts_file artifacts_metadata artifacts_size commands resource resource_group_id processed security_scans author - pipeline_id].freeze + pipeline_id report_results].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } diff --git a/spec/services/jira_import/users_importer_spec.rb b/spec/services/jira_import/users_importer_spec.rb new file mode 100644 index 00000000000..e57a14a8f80 --- /dev/null +++ b/spec/services/jira_import/users_importer_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JiraImport::UsersImporter do + include JiraServiceHelper + + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project) } + let_it_be(:start_at) { 7 } + + let(:importer) { described_class.new(user, project, start_at) } + + subject { importer.execute } + + describe '#execute' do + before do + stub_jira_service_test + project.add_maintainer(user) + end + + context 'when Jira import is not configured properly' do + it 'raises an error' do + expect { subject }.to raise_error(Projects::ImportService::Error) + end + end + + context 'when Jira import is configured correctly' do + let_it_be(:jira_service) { create(:jira_service, project: project, active: true) } + let(:client) { double } + + before do + expect(importer).to receive(:client).and_return(client) + end + + context 'when jira client raises an error' do + it 'returns an error response' do + expect(client).to receive(:get).and_raise(Timeout::Error) + + expect(subject.error?).to be_truthy + expect(subject.message).to include('There was an error when communicating to Jira') + end + end + + context 'when jira client returns result' do + before do + allow(client).to receive(:get).with('/rest/api/2/users?maxResults=50&startAt=7') + .and_return(jira_users) + end + + context 'when jira client returns an empty array' do + let(:jira_users) { [] } + + it 'retturns nil payload' do + expect(subject.success?).to be_truthy + expect(subject.payload).to be_nil + end + end + + context 'when jira client returns an results' do + let(:jira_users) { [{ 'name' => 'user1' }, { 'name' => 'user2' }] } + let(:mapped_users) { [{ jira_display_name: 'user1', gitlab_id: 5 }] } + + before do + expect(JiraImport::UsersMapper).to receive(:new).with(project, jira_users) + .and_return(double(execute: mapped_users)) + end + + it 'returns the mapped users' do + expect(subject.success?).to be_truthy + expect(subject.payload).to eq(mapped_users) + end + end + end + end + end +end diff --git a/spec/services/jira_import/users_mapper_spec.rb b/spec/services/jira_import/users_mapper_spec.rb new file mode 100644 index 00000000000..75dbc41aa2e --- /dev/null +++ b/spec/services/jira_import/users_mapper_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JiraImport::UsersMapper do + let_it_be(:project) { create(:project) } + + subject { described_class.new(project, jira_users).execute } + + describe '#execute' do + context 'jira_users is nil' do + let(:jira_users) { nil } + + it 'returns an empty array' do + expect(subject).to be_empty + end + end + + context 'when jira_users is present' do + let(:jira_users) do + [ + { 'accountId' => 'abcd', 'displayName' => 'user1' }, + { 'accountId' => 'efg' }, + { 'accountId' => 'hij', 'displayName' => 'user3', 'emailAddress' => 'user3@example.com' } + ] + end + + # TODO: now we only create an array in a proper format + # mapping is tracked in https://gitlab.com/gitlab-org/gitlab/-/issues/219023 + let(:mapped_users) do + [ + { jira_account_id: 'abcd', jira_display_name: 'user1', jira_email: nil, gitlab_id: nil }, + { jira_account_id: 'efg', jira_display_name: nil, jira_email: nil, gitlab_id: nil }, + { jira_account_id: 'hij', jira_display_name: 'user3', jira_email: 'user3@example.com', gitlab_id: nil } + ] + end + + it 'returns users mapped to Gitlab' do + expect(subject).to eq(mapped_users) + end + end + end +end diff --git a/spec/support/helpers/partitioning_helpers.rb b/spec/support/helpers/partitioning_helpers.rb new file mode 100644 index 00000000000..98a13915d76 --- /dev/null +++ b/spec/support/helpers/partitioning_helpers.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module PartitioningHelpers + def expect_table_partitioned_by(table, columns, part_type: :range) + columns_with_part_type = columns.map { |c| [part_type.to_s, c] } + actual_columns = find_partitioned_columns(table) + + expect(columns_with_part_type).to match_array(actual_columns) + end + + def expect_range_partition_of(partition_name, table_name, min_value, max_value) + definition = find_partition_definition(partition_name) + + expect(definition).not_to be_nil + expect(definition['base_table']).to eq(table_name.to_s) + expect(definition['condition']).to eq("FOR VALUES FROM (#{min_value}) TO (#{max_value})") + end + + private + + def find_partitioned_columns(table) + connection.select_rows(<<~SQL) + select + case partstrat + when 'l' then 'list' + when 'r' then 'range' + when 'h' then 'hash' + end as partstrat, + cols.column_name + from ( + select partrelid, partstrat, unnest(partattrs) as col_pos + from pg_partitioned_table + ) pg_part + inner join pg_class + on pg_part.partrelid = pg_class.oid + inner join information_schema.columns cols + on cols.table_name = pg_class.relname + and cols.ordinal_position = pg_part.col_pos + where pg_class.relname = '#{table}'; + SQL + end + + def find_partition_definition(partition) + connection.select_one(<<~SQL) + select + parent_class.relname as base_table, + pg_get_expr(pg_class.relpartbound, inhrelid) as condition + from pg_class + inner join pg_inherits i on pg_class.oid = inhrelid + inner join pg_class parent_class on parent_class.oid = inhparent + where pg_class.relname = '#{partition}' and pg_class.relispartition; + SQL + end +end diff --git a/spec/validators/json_schema_validator_spec.rb b/spec/validators/json_schema_validator_spec.rb new file mode 100644 index 00000000000..0e942e755f5 --- /dev/null +++ b/spec/validators/json_schema_validator_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JsonSchemaValidator do + describe '#validates_each' do + let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) } + + subject { validator.validate(build_report_result) } + + context 'when file_path is set' do + let(:validator) { described_class.new(attributes: [:data], filename: "build_report_result_data") } + + context 'when data is valid' do + it 'returns no errors' do + subject + + expect(build_report_result.errors).to be_empty + end + end + + context 'when data is invalid' do + it 'returns json schema is invalid' do + build_report_result.data = { invalid: 'data' } + + validator.validate(build_report_result) + + expect(build_report_result.errors.size).to eq(1) + expect(build_report_result.errors.full_messages).to eq(["Data must be a valid json schema"]) + end + end + end + + context 'when file_path is not set' do + let(:validator) { described_class.new(attributes: [:data]) } + + it 'raises an ArgumentError' do + expect { subject }.to raise_error(ArgumentError) + end + end + end +end |