summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-29 21:08:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-29 21:08:35 +0000
commitd8b32df644a632b143d6b9967311301a2fc83a6b (patch)
tree130722547715d1f65104529d8a09b1ba123776d6 /spec
parentb64a8161c9442d82897a341d6bf935dd3e748b06 (diff)
downloadgitlab-ce-d8b32df644a632b143d6b9967311301a2fc83a6b.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb40
-rw-r--r--spec/factories/alert_management/alerts.rb7
-rw-r--r--spec/factories/ci/build_report_results.rb35
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_spec.js31
-rw-r--r--spec/frontend/alert_management/mocks/alerts.json9
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap2
-rw-r--r--spec/frontend/monitoring/components/links_section_spec.js64
-rw-r--r--spec/graphql/types/alert_management/alert_type_spec.rb1
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/base_query_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb4
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb46
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb50
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/foreign_key_helpers_spec.rb (renamed from spec/lib/gitlab/database/partitioning_migration_helpers_spec.rb)8
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb135
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/models/alert_management/alert_assignee_spec.rb21
-rw-r--r--spec/models/alert_management/alert_spec.rb1
-rw-r--r--spec/models/ci/build_report_result_spec.rb32
-rw-r--r--spec/models/ci/build_spec.rb1
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/requests/api/graphql/project/alert_management/alerts_spec.rb2
-rw-r--r--spec/requests/api/project_repository_storage_moves_spec.rb87
-rw-r--r--spec/services/ci/retry_build_service_spec.rb2
-rw-r--r--spec/services/jira_import/users_importer_spec.rb77
-rw-r--r--spec/services/jira_import/users_mapper_spec.rb43
-rw-r--r--spec/support/helpers/partitioning_helpers.rb54
-rw-r--r--spec/validators/json_schema_validator_spec.rb42
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