Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-10-28 00:12:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-10-28 00:12:19 +0300
commitf3d0329fcb9c703757c4859b12d19c5c3fe42c40 (patch)
tree499c86fad4ab967a9ffdb2cddeead2afe792968c /spec
parent7badd9fd55f9b877f2de8c8d0126c720a57e538d (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb1
-rw-r--r--spec/features/projects/commit/user_sees_pipelines_tab_spec.rb1
-rw-r--r--spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js28
-rw-r--r--spec/frontend/ci/pipelines_page/components/pipeline_triggerer_spec.js13
-rw-r--r--spec/graphql/types/container_registry/protection/rule_access_level_enum_spec.rb9
-rw-r--r--spec/graphql/types/container_registry/protection/rule_type_spec.rb35
-rw-r--r--spec/lib/banzai/filter/asset_proxy_filter_spec.rb101
-rw-r--r--spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb2
-rw-r--r--spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb2
-rw-r--r--spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb6
-rw-r--r--spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb16
-rw-r--r--spec/migrations/schedule_fixing_security_scan_statuses_spec.rb11
-rw-r--r--spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb180
-rw-r--r--spec/services/container_registry/protection/create_rule_service_spec.rb145
-rw-r--r--spec/support/database/partitioning_routing_analyzer.rb7
-rw-r--r--spec/support_specs/helpers/migrations_helpers_spec.rb4
16 files changed, 449 insertions, 112 deletions
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index c21c9043f01..a06d1808b6b 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -51,7 +51,6 @@ RSpec.describe 'Merge request > User sees pipelines', :js, feature_category: :co
page.within(find('[data-testid="pipeline-table-row"]', match: :first)) do
expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Passed')
expect(page).to have_content(pipeline.id)
- expect(page).to have_content('API')
expect(page).to have_css('[data-testid="pipeline-mini-graph"]')
expect(page).to have_css('[data-testid="pipelines-manual-actions-dropdown"]')
expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]')
diff --git a/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb b/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb
index 56a3a252f3e..5d722ddbedb 100644
--- a/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb
+++ b/spec/features/projects/commit/user_sees_pipelines_tab_spec.rb
@@ -38,7 +38,6 @@ RSpec.describe 'Commit > Pipelines tab', :js, feature_category: :source_code_man
page.within('[data-testid="pipeline-table-row"]') do
expect(page).to have_selector('[data-testid="ci-icon"]', text: 'Passed')
expect(page).to have_content(pipeline.id)
- expect(page).to have_content('API')
expect(page).to have_css('[data-testid="pipeline-mini-graph"]')
expect(page).to have_css('[data-testid="pipelines-manual-actions-dropdown"]')
expect(page).to have_css('[data-testid="pipeline-multi-actions-dropdown"]')
diff --git a/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js b/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js
index 6b0d5b18f7d..4377acb9041 100644
--- a/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js
+++ b/spec/frontend/ci/pipelines_page/components/pipeline_labels_spec.js
@@ -2,6 +2,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import PipelineLabelsComponent from '~/ci/pipelines_page/components/pipeline_labels.vue';
import { mockPipeline } from 'jest/ci/pipeline_details/mock_data';
+import { SCHEDULE_ORIGIN, API_ORIGIN } from '~/ci/pipelines_page/constants';
const projectPath = 'test/test';
@@ -19,6 +20,7 @@ describe('Pipeline label component', () => {
const findFailureTag = () => wrapper.findByTestId('pipeline-url-failure');
const findForkTag = () => wrapper.findByTestId('pipeline-url-fork');
const findTrainTag = () => wrapper.findByTestId('pipeline-url-train');
+ const findApiTag = () => wrapper.findByTestId('pipeline-api-badge');
const defaultProps = mockPipeline(projectPath);
@@ -121,14 +123,14 @@ describe('Pipeline label component', () => {
it('should render scheduled badge when pipeline was triggered by a schedule', () => {
const scheduledPipeline = defaultProps.pipeline;
- scheduledPipeline.source = 'schedule';
+ scheduledPipeline.source = SCHEDULE_ORIGIN;
createComponent({
...scheduledPipeline,
});
expect(findScheduledTag().exists()).toBe(true);
- expect(findScheduledTag().text()).toContain('Scheduled');
+ expect(findScheduledTag().text()).toContain('scheduled');
});
it('should render the fork badge when the pipeline was run in a fork', () => {
@@ -201,4 +203,26 @@ describe('Pipeline label component', () => {
expect(findMergedResultsTag().exists()).toBe(false);
});
+
+ it.each`
+ display | source
+ ${true} | ${API_ORIGIN}
+ ${false} | ${SCHEDULE_ORIGIN}
+ `(
+ 'should display the api badge: $display, when the pipeline has a source of $source',
+ ({ display, source }) => {
+ const apiPipeline = defaultProps.pipeline;
+ apiPipeline.source = source;
+
+ createComponent({
+ ...apiPipeline,
+ });
+
+ if (display) {
+ expect(findApiTag().text()).toBe(API_ORIGIN);
+ } else {
+ expect(findApiTag().exists()).toBe(false);
+ }
+ },
+ );
});
diff --git a/spec/frontend/ci/pipelines_page/components/pipeline_triggerer_spec.js b/spec/frontend/ci/pipelines_page/components/pipeline_triggerer_spec.js
index cb04171f031..a4780cddc3c 100644
--- a/spec/frontend/ci/pipelines_page/components/pipeline_triggerer_spec.js
+++ b/spec/frontend/ci/pipelines_page/components/pipeline_triggerer_spec.js
@@ -29,7 +29,6 @@ describe('Pipelines Triggerer', () => {
const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
const findAvatar = () => wrapper.findComponent(GlAvatar);
- const findTriggerer = () => wrapper.findByText('API');
describe('when user was a triggerer', () => {
beforeEach(() => {
@@ -42,7 +41,6 @@ describe('Pipelines Triggerer', () => {
it('should render only user avatar', () => {
expect(findAvatarLink().exists()).toBe(true);
- expect(findTriggerer().exists()).toBe(false);
});
it('should set correct props on avatar link component', () => {
@@ -62,15 +60,4 @@ describe('Pipelines Triggerer', () => {
expect(findAvatar().attributes().src).toBe(mockData.pipeline.user.avatar_url);
});
});
-
- describe('when API was a triggerer', () => {
- beforeEach(() => {
- createComponent({ pipeline: {} });
- });
-
- it('should render label only', () => {
- expect(findAvatarLink().exists()).toBe(false);
- expect(findTriggerer().exists()).toBe(true);
- });
- });
});
diff --git a/spec/graphql/types/container_registry/protection/rule_access_level_enum_spec.rb b/spec/graphql/types/container_registry/protection/rule_access_level_enum_spec.rb
new file mode 100644
index 00000000000..295401f89f9
--- /dev/null
+++ b/spec/graphql/types/container_registry/protection/rule_access_level_enum_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRegistryProtectionRuleAccessLevel'], feature_category: :container_registry do
+ it 'exposes all options' do
+ expect(described_class.values.keys).to match_array(%w[DEVELOPER MAINTAINER OWNER])
+ end
+end
diff --git a/spec/graphql/types/container_registry/protection/rule_type_spec.rb b/spec/graphql/types/container_registry/protection/rule_type_spec.rb
new file mode 100644
index 00000000000..58b53af80fb
--- /dev/null
+++ b/spec/graphql/types/container_registry/protection/rule_type_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['ContainerRegistryProtectionRule'], feature_category: :container_registry do
+ specify { expect(described_class.graphql_name).to eq('ContainerRegistryProtectionRule') }
+
+ specify { expect(described_class.description).to be_present }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_container_image) }
+
+ describe 'id' do
+ subject { described_class.fields['id'] }
+
+ it { is_expected.to have_non_null_graphql_type(::Types::GlobalIDType[::ContainerRegistry::Protection::Rule]) }
+ end
+
+ describe 'container_path_pattern' do
+ subject { described_class.fields['containerPathPattern'] }
+
+ it { is_expected.to have_non_null_graphql_type(GraphQL::Types::String) }
+ end
+
+ describe 'push_protected_up_to_access_level' do
+ subject { described_class.fields['pushProtectedUpToAccessLevel'] }
+
+ it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::RuleAccessLevelEnum) }
+ end
+
+ describe 'delete_protected_up_to_access_level' do
+ subject { described_class.fields['deleteProtectedUpToAccessLevel'] }
+
+ it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::RuleAccessLevelEnum) }
+ end
+end
diff --git a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
index baa22e08971..cb696e21e65 100644
--- a/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
+++ b/spec/lib/banzai/filter/asset_proxy_filter_spec.rb
@@ -62,6 +62,8 @@ RSpec.describe Banzai::Filter::AssetProxyFilter, feature_category: :team_plannin
end
context 'when properly configured' do
+ using RSpec::Parameterized::TableSyntax
+
before do
stub_asset_proxy_setting(enabled: true)
stub_asset_proxy_setting(secret_key: 'shared-secret')
@@ -71,84 +73,33 @@ RSpec.describe Banzai::Filter::AssetProxyFilter, feature_category: :team_plannin
@context = described_class.transform_context({})
end
- it 'replaces img src' do
- src = 'http://example.com/test.png'
- new_src = 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67'
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq new_src
- expect(doc.at_css('img')['data-canonical-src']).to eq src
- end
-
- it 'replaces invalid URLs' do
- src = '///example.com/test.png'
- new_src = 'https://assets.example.com/3368d2c7b9bed775bdd1e811f36a4b80a0dcd8ab/2f2f2f6578616d706c652e636f6d2f746573742e706e67'
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq new_src
- expect(doc.at_css('img')['data-canonical-src']).to eq src
- end
-
- it 'replaces by default, even strings that do not look like URLs' do
- src = 'oigjsie8787%$**(#(%0'
- new_src = 'https://assets.example.com/1b893f9a71d66c99437f27e19b9a061a6f5d9391/6f69676a7369653837383725242a2a2823282530'
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq new_src
- expect(doc.at_css('img')['data-canonical-src']).to eq src
- end
-
- it 'replaces URL with non-ASCII characters' do
- src = 'https://example.com/x?¬'
- new_src = 'https://assets.example.com/2f29a8c7f13f3ae14dc18c154dbbd657d703e75f/68747470733a2f2f6578616d706c652e636f6d2f783fc2ac'
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq new_src
- expect(doc.at_css('img')['data-canonical-src']).to eq src
- end
-
- it 'replaces out-of-spec URL that would still be rendered in the browser' do
- src = 'https://example.com/##'
- new_src = 'https://assets.example.com/d7d0c845cc553d9430804c07e9456545ef3e6fe6/68747470733a2f2f6578616d706c652e636f6d2f2323'
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq new_src
- expect(doc.at_css('img')['data-canonical-src']).to eq src
+ where(:data_canonical_src, :src) do
+ 'http://example.com/test.png' | 'https://assets.example.com/08df250eeeef1a8cf2c761475ac74c5065105612/687474703a2f2f6578616d706c652e636f6d2f746573742e706e67'
+ '///example.com/test.png' | 'https://assets.example.com/3368d2c7b9bed775bdd1e811f36a4b80a0dcd8ab/2f2f2f6578616d706c652e636f6d2f746573742e706e67'
+ '//example.com/test.png' | 'https://assets.example.com/a2e9aa56319e31bbd05be72e633f2864ff08becb/2f2f6578616d706c652e636f6d2f746573742e706e67'
+ # If it can't be parsed, default to use asset proxy
+ 'oigjsie8787%$**(#(%0' | 'https://assets.example.com/1b893f9a71d66c99437f27e19b9a061a6f5d9391/6f69676a7369653837383725242a2a2823282530'
+ 'https://example.com/x?¬' | 'https://assets.example.com/2f29a8c7f13f3ae14dc18c154dbbd657d703e75f/68747470733a2f2f6578616d706c652e636f6d2f783fc2ac'
+ # The browser loads this as if it was a valid URL
+ 'http:example.com' | 'https://assets.example.com/bcefecd18484ec2850887d6730273e5e70f5ed1a/687474703a6578616d706c652e636f6d'
+ 'https:example.com' | 'https://assets.example.com/648e074361143780357db0b5cf73d4438d5484d3/68747470733a6578616d706c652e636f6d'
+ 'https://example.com/##' | 'https://assets.example.com/d7d0c845cc553d9430804c07e9456545ef3e6fe6/68747470733a2f2f6578616d706c652e636f6d2f2323'
+ nil | "test.png"
+ nil | "/test.png"
+ nil | "#{Gitlab.config.gitlab.url}/test.png"
+ nil | 'http://gitlab.com/test.png'
+ nil | 'http://gitlab.com/test.png?url=http://example.com/test.png'
+ nil | 'http://images.mydomain.com/test.png'
end
- it 'skips internal images' do
- src = "#{Gitlab.config.gitlab.url}/test.png"
- doc = filter(image(src), @context)
+ with_them do
+ it 'correctly modifies the img tag' do
+ original_url = data_canonical_src || src
+ doc = filter(image(original_url), @context)
- expect(doc.at_css('img')['src']).to eq src
- end
-
- it 'skip relative urls' do
- src = "/test.png"
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq src
- end
-
- it 'skips single domain' do
- src = "http://gitlab.com/test.png"
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq src
- end
-
- it 'skips single domain and ignores url in query string' do
- src = "http://gitlab.com/test.png?url=http://example.com/test.png"
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq src
- end
-
- it 'skips wildcarded domain' do
- src = "http://images.mydomain.com/test.png"
- doc = filter(image(src), @context)
-
- expect(doc.at_css('img')['src']).to eq src
+ expect(doc.at_css('img')['src']).to eq src
+ expect(doc.at_css('img')['data-canonical-src']).to eq data_canonical_src
+ end
end
end
end
diff --git a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb
index af8b5240e40..4c989ba9cef 100644
--- a/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb
+++ b/spec/lib/gitlab/background_migration/remove_backfilled_job_artifacts_expire_at_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveBackfilledJobArtifactsExpireAt
describe '#perform' do
let(:job_artifact) { table(:ci_job_artifacts, database: :ci) }
- let(:jobs) { table(:ci_builds, database: :ci) { |model| model.primary_key = :id } }
+ let(:jobs) { table(:p_ci_builds, database: :ci) { |model| model.primary_key = :id } }
let(:test_worker) do
described_class.new(
diff --git a/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb b/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb
index c6327de98d1..6e943307ae6 100644
--- a/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb
+++ b/spec/lib/gitlab/database/migrations/reestablished_connection_stack_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe Gitlab::Database::Migrations::ReestablishedConnectionStack do
# establish connection
ApplicationRecord.connection.select_one("SELECT 1 FROM projects LIMIT 1")
- Ci::ApplicationRecord.connection.select_one("SELECT 1 FROM ci_builds LIMIT 1")
+ Ci::ApplicationRecord.connection.select_one("SELECT 1 FROM p_ci_builds LIMIT 1")
end
expect(new_handler).not_to eq(original_handler), "is reconnected"
diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb
index f325060e592..1909e134e66 100644
--- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb
+++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb
@@ -35,7 +35,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
},
"for query accessing gitlab_ci and gitlab_main" => {
model: ApplicationRecord,
- sql: "SELECT 1 FROM projects LEFT JOIN ci_builds ON ci_builds.project_id=projects.id",
+ sql: "SELECT 1 FROM projects LEFT JOIN p_ci_builds ON p_ci_builds.project_id=projects.id",
expectations: {
gitlab_schemas: "gitlab_ci,gitlab_main_cell",
db_config_name: "main"
@@ -43,7 +43,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
},
"for query accessing gitlab_ci and gitlab_main the gitlab_schemas is always ordered" => {
model: ApplicationRecord,
- sql: "SELECT 1 FROM ci_builds LEFT JOIN projects ON ci_builds.project_id=projects.id",
+ sql: "SELECT 1 FROM p_ci_builds LEFT JOIN projects ON p_ci_builds.project_id=projects.id",
expectations: {
gitlab_schemas: "gitlab_ci,gitlab_main_cell",
db_config_name: "main"
@@ -51,7 +51,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasMetrics, query_ana
},
"for query accessing CI database" => {
model: Ci::ApplicationRecord,
- sql: "SELECT 1 FROM ci_builds",
+ sql: "SELECT 1 FROM p_ci_builds",
expectations: {
gitlab_schemas: "gitlab_ci",
db_config_name: "ci"
diff --git a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb
index e3ff5ab4779..0664508fa8d 100644
--- a/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb
+++ b/spec/lib/gitlab/database/query_analyzers/gitlab_schemas_validate_connection_spec.rb
@@ -26,14 +26,14 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection
},
"for query accessing gitlab_ci and gitlab_main" => {
model: ApplicationRecord,
- sql: "SELECT 1 FROM projects LEFT JOIN ci_builds ON ci_builds.project_id=projects.id",
- expect_error: /The query tried to access \["projects", "ci_builds"\]/,
+ sql: "SELECT 1 FROM projects LEFT JOIN p_ci_builds ON p_ci_builds.project_id=projects.id",
+ expect_error: /The query tried to access \["projects", "p_ci_builds"\]/,
setup: -> (_) { skip_if_shared_database(:ci) }
},
"for query accessing gitlab_ci and gitlab_main the gitlab_schemas is always ordered" => {
model: ApplicationRecord,
- sql: "SELECT 1 FROM ci_builds LEFT JOIN projects ON ci_builds.project_id=projects.id",
- expect_error: /The query tried to access \["ci_builds", "projects"\]/,
+ sql: "SELECT 1 FROM p_ci_builds LEFT JOIN projects ON p_ci_builds.project_id=projects.id",
+ expect_error: /The query tried to access \["p_ci_builds", "projects"\]/,
setup: -> (_) { skip_if_shared_database(:ci) }
},
"for query accessing main table from CI database" => {
@@ -44,13 +44,13 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection
},
"for query accessing CI database" => {
model: Ci::ApplicationRecord,
- sql: "SELECT 1 FROM ci_builds",
+ sql: "SELECT 1 FROM p_ci_builds",
expect_error: nil
},
"for query accessing CI table from main database" => {
model: ::ApplicationRecord,
- sql: "SELECT 1 FROM ci_builds",
- expect_error: /The query tried to access \["ci_builds"\]/,
+ sql: "SELECT 1 FROM p_ci_builds",
+ expect_error: /The query tried to access \["p_ci_builds"\]/,
setup: -> (_) { skip_if_shared_database(:ci) }
},
"for query accessing unknown gitlab_schema" => {
@@ -89,7 +89,7 @@ RSpec.describe Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection
it "throws an error when trying to access a table that belongs to the gitlab_ci schema from the main database" do
expect do
- ApplicationRecord.connection.execute("select * from ci_builds limit 1")
+ ApplicationRecord.connection.execute("select * from p_ci_builds limit 1")
end.to raise_error(Gitlab::Database::QueryAnalyzers::GitlabSchemasValidateConnection::CrossSchemaAccessError)
end
end
diff --git a/spec/migrations/schedule_fixing_security_scan_statuses_spec.rb b/spec/migrations/schedule_fixing_security_scan_statuses_spec.rb
index 56d30e71676..f43f58d3be2 100644
--- a/spec/migrations/schedule_fixing_security_scan_statuses_spec.rb
+++ b/spec/migrations/schedule_fixing_security_scan_statuses_spec.rb
@@ -4,20 +4,21 @@ require 'spec_helper'
require_migration!
RSpec.describe ScheduleFixingSecurityScanStatuses,
- :suppress_gitlab_schemas_validate_connection, feature_category: :vulnerability_management do
+ :suppress_gitlab_schemas_validate_connection, :suppress_partitioning_routing_analyzer,
+ feature_category: :vulnerability_management do
let!(:namespaces) { table(:namespaces) }
let!(:projects) { table(:projects) }
- let!(:pipelines) { table(:ci_pipelines) }
- let!(:builds) { table(:ci_builds) }
+ let!(:pipelines) { table(:ci_pipelines, database: :ci) }
+ let!(:builds) { table(:ci_builds, database: :ci) { |model| model.primary_key = :id } }
let!(:security_scans) { table(:security_scans) }
let!(:namespace) { namespaces.create!(name: "foo", path: "bar") }
let!(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
let!(:pipeline) do
- pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a', status: 'success', partition_id: 1)
+ pipelines.create!(project_id: project.id, ref: 'master', sha: 'adf43c3a', status: 'success', partition_id: 100)
end
- let!(:ci_build) { builds.create!(commit_id: pipeline.id, retried: false, type: 'Ci::Build', partition_id: 1) }
+ let!(:ci_build) { builds.create!(commit_id: pipeline.id, retried: false, type: 'Ci::Build', partition_id: 100) }
let!(:security_scan_1) { security_scans.create!(build_id: ci_build.id, scan_type: 1, created_at: 91.days.ago) }
let!(:security_scan_2) { security_scans.create!(build_id: ci_build.id, scan_type: 2) }
diff --git a/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb
new file mode 100644
index 00000000000..0c708c3dc41
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb
@@ -0,0 +1,180 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Creating the container registry protection rule', :aggregate_failures, feature_category: :container_registry do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user, maintainer_projects: [project]) }
+
+ let(:container_registry_protection_rule_attributes) do
+ build_stubbed(:container_registry_protection_rule, project: project)
+ end
+
+ let(:kwargs) do
+ {
+ project_path: project.full_path,
+ container_path_pattern: container_registry_protection_rule_attributes.container_path_pattern,
+ push_protected_up_to_access_level: 'MAINTAINER',
+ delete_protected_up_to_access_level: 'MAINTAINER'
+ }
+ end
+
+ let(:mutation) do
+ graphql_mutation(:create_container_registry_protection_rule, kwargs,
+ <<~QUERY
+ containerRegistryProtectionRule {
+ id
+ containerPathPattern
+ }
+ clientMutationId
+ errors
+ QUERY
+ )
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:create_container_registry_protection_rule) }
+
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ shared_examples 'a successful response' do
+ it { subject.tap { expect_graphql_errors_to_be_empty } }
+
+ it do
+ subject
+
+ expect(mutation_response).to include(
+ 'errors' => be_blank,
+ 'containerRegistryProtectionRule' => {
+ 'id' => be_present,
+ 'containerPathPattern' => kwargs[:container_path_pattern]
+ }
+ )
+ end
+
+ it 'creates container registry protection rule in the database' do
+ expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.by(1)
+
+ expect(::ContainerRegistry::Protection::Rule.where(project: project,
+ container_path_pattern: kwargs[:container_path_pattern])).to exist
+ end
+ end
+
+ shared_examples 'an erroneous response' do
+ it { expect { subject }.not_to change { ::ContainerRegistry::Protection::Rule.count } }
+ end
+
+ it_behaves_like 'a successful response'
+
+ context 'with invalid input fields `pushProtectedUpToAccessLevel` and `deleteProtectedUpToAccessLevel`' do
+ let(:kwargs) do
+ super().merge(
+ push_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL',
+ delete_protected_up_to_access_level: 'UNKNOWN_ACCESS_LEVEL'
+ )
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it {
+ subject
+
+ expect_graphql_errors_to_include([/pushProtectedUpToAccessLevel/, /deleteProtectedUpToAccessLevel/])
+ }
+ end
+
+ context 'with invalid input field `containerPathPattern`' do
+ let(:kwargs) do
+ super().merge(container_path_pattern: '')
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect_graphql_errors_to_be_empty } }
+
+ it {
+ subject.tap do
+ expect(mutation_response['errors']).to eq ["Container path pattern can't be blank"]
+ end
+ }
+ end
+
+ context 'with existing containers protection rule' do
+ let_it_be(:existing_container_registry_protection_rule) do
+ create(:container_registry_protection_rule, project: project,
+ push_protected_up_to_access_level: Gitlab::Access::DEVELOPER)
+ end
+
+ context 'when container name pattern is slightly different' do
+ let(:kwargs) do
+ # The field `container_path_pattern` is unique; this is why we change the value in a minimum way
+ super().merge(
+ container_path_pattern: "#{existing_container_registry_protection_rule.container_path_pattern}-unique"
+ )
+ end
+
+ it_behaves_like 'a successful response'
+
+ it 'adds another container registry protection rule to the database' do
+ expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.from(1).to(2)
+ end
+ end
+
+ context 'when field `container_path_pattern` is taken' do
+ let(:kwargs) do
+ super().merge(container_path_pattern: existing_container_registry_protection_rule.container_path_pattern,
+ push_protected_up_to_access_level: 'MAINTAINER')
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect_graphql_errors_to_be_empty } }
+
+ it 'returns without error' do
+ subject
+
+ expect(mutation_response['errors']).to eq ['Container path pattern has already been taken']
+ end
+
+ it 'does not create new container protection rules' do
+ expect(::ContainerRegistry::Protection::Rule.where(project: project,
+ container_path_pattern: kwargs[:container_path_pattern],
+ push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist
+ end
+ end
+ end
+
+ context 'when user does not have permission' do
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+ let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
+ let_it_be(:anonymous) { create(:user) }
+
+ where(:user) do
+ [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)]
+ end
+
+ with_them do
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } }
+ end
+ end
+
+ context "when feature flag ':container_registry_protected_containers' disabled" do
+ before do
+ stub_feature_flags(container_registry_protected_containers: false)
+ end
+
+ it_behaves_like 'an erroneous response'
+
+ it { subject.tap { expect(::ContainerRegistry::Protection::Rule.where(project: project)).not_to exist } }
+
+ it 'returns error of disabled feature flag' do
+ subject.tap do
+ expect_graphql_errors_to_include(/'container_registry_protected_containers' feature flag is disabled/)
+ end
+ end
+ end
+end
diff --git a/spec/services/container_registry/protection/create_rule_service_spec.rb b/spec/services/container_registry/protection/create_rule_service_spec.rb
new file mode 100644
index 00000000000..3c319caf25c
--- /dev/null
+++ b/spec/services/container_registry/protection/create_rule_service_spec.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::Protection::CreateRuleService, '#execute', feature_category: :container_registry do
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:current_user) { create(:user, maintainer_projects: [project]) }
+
+ let(:service) { described_class.new(project, current_user, params) }
+ let(:params) { attributes_for(:container_registry_protection_rule) }
+
+ subject { service.execute }
+
+ shared_examples 'a successful service response' do
+ it { is_expected.to be_success }
+
+ it { is_expected.to have_attributes(errors: be_blank) }
+
+ it do
+ is_expected.to have_attributes(
+ payload: {
+ container_registry_protection_rule:
+ be_a(ContainerRegistry::Protection::Rule)
+ .and(have_attributes(
+ container_path_pattern: params[:container_path_pattern],
+ push_protected_up_to_access_level: params[:push_protected_up_to_access_level].to_s,
+ delete_protected_up_to_access_level: params[:delete_protected_up_to_access_level].to_s
+ ))
+ }
+ )
+ end
+
+ it 'creates a new container registry protection rule in the database' do
+ expect { subject }.to change { ContainerRegistry::Protection::Rule.count }.by(1)
+
+ expect(
+ ContainerRegistry::Protection::Rule.where(
+ project: project,
+ container_path_pattern: params[:container_path_pattern],
+ push_protected_up_to_access_level: params[:push_protected_up_to_access_level]
+ )
+ ).to exist
+ end
+ end
+
+ shared_examples 'an erroneous service response' do
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes(errors: be_present, payload: include(container_registry_protection_rule: nil)) }
+
+ it 'does not create a new container registry protection rule in the database' do
+ expect { subject }.not_to change { ContainerRegistry::Protection::Rule.count }
+ end
+
+ it 'does not create a container registry protection rule with the given params' do
+ subject
+
+ expect(
+ ContainerRegistry::Protection::Rule.where(
+ project: project,
+ container_path_pattern: params[:container_path_pattern],
+ push_protected_up_to_access_level: params[:push_protected_up_to_access_level]
+ )
+ ).not_to exist
+ end
+ end
+
+ it_behaves_like 'a successful service response'
+
+ context 'when fields are invalid' do
+ context 'when container_path_pattern is invalid' do
+ let(:params) { super().merge(container_path_pattern: '') }
+
+ it_behaves_like 'an erroneous service response'
+
+ it { is_expected.to have_attributes(message: match(/Container path pattern can't be blank/)) }
+ end
+
+ context 'when delete_protected_up_to_access_level is invalid' do
+ let(:params) { super().merge(delete_protected_up_to_access_level: 1000) }
+
+ it_behaves_like 'an erroneous service response'
+
+ it { is_expected.to have_attributes(message: match(/is not a valid delete_protected_up_to_access_level/)) }
+ end
+
+ context 'when push_protected_up_to_access_level is invalid' do
+ let(:params) { super().merge(push_protected_up_to_access_level: 1000) }
+
+ it_behaves_like 'an erroneous service response'
+
+ it { is_expected.to have_attributes(message: match(/is not a valid push_protected_up_to_access_level/)) }
+ end
+ end
+
+ context 'with existing container registry protection rule in the database' do
+ let_it_be_with_reload(:existing_container_registry_protection_rule) do
+ create(:container_registry_protection_rule, project: project)
+ end
+
+ context 'when container registry name pattern is slightly different' do
+ let(:params) do
+ super().merge(
+ # The field `container_path_pattern` is unique; this is why we change the value in a minimum way
+ container_path_pattern: "#{existing_container_registry_protection_rule.container_path_pattern}-unique",
+ push_protected_up_to_access_level:
+ existing_container_registry_protection_rule.push_protected_up_to_access_level
+ )
+ end
+
+ it_behaves_like 'a successful service response'
+ end
+
+ context 'when field `container_path_pattern` is taken' do
+ let(:params) do
+ super().merge(
+ container_path_pattern: existing_container_registry_protection_rule.container_path_pattern,
+ push_protected_up_to_access_level: :maintainer
+ )
+ end
+
+ it_behaves_like 'an erroneous service response'
+
+ it { is_expected.to have_attributes(errors: ['Container path pattern has already been taken']) }
+
+ it { expect { subject }.not_to change { existing_container_registry_protection_rule.updated_at } }
+ end
+ end
+
+ context 'with disallowed params' do
+ let(:params) { super().merge(project_id: 1, unsupported_param: 'unsupported_param_value') }
+
+ it_behaves_like 'a successful service response'
+ end
+
+ context 'with forbidden user access level (project developer role)' do
+ # Because of the access level hierarchy, we can assume that
+ # other access levels below developer role will also not be able to
+ # create container registry protection rules.
+ let_it_be(:current_user) { create(:user).tap { |u| project.add_developer(u) } }
+
+ it_behaves_like 'an erroneous service response'
+
+ it { is_expected.to have_attributes(message: match(/Unauthorized/)) }
+ end
+end
diff --git a/spec/support/database/partitioning_routing_analyzer.rb b/spec/support/database/partitioning_routing_analyzer.rb
new file mode 100644
index 00000000000..b1edd817386
--- /dev/null
+++ b/spec/support/database/partitioning_routing_analyzer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+RSpec.configure do |config|
+ config.around(:each, :suppress_partitioning_routing_analyzer) do |example|
+ Gitlab::Database::QueryAnalyzers::Ci::PartitioningRoutingAnalyzer.with_suppressed(&example)
+ end
+end
diff --git a/spec/support_specs/helpers/migrations_helpers_spec.rb b/spec/support_specs/helpers/migrations_helpers_spec.rb
index 2af16151350..725caef7a63 100644
--- a/spec/support_specs/helpers/migrations_helpers_spec.rb
+++ b/spec/support_specs/helpers/migrations_helpers_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe MigrationsHelpers, feature_category: :database do
end
it 'create a class based on the CI base model' do
- klass = helper.table(:ci_builds, database: :ci)
+ klass = helper.table(:p_ci_builds, database: :ci) { |model| model.primary_key = :id }
expect(klass.connection_specification_name).to eq('Ci::ApplicationRecord')
end
end
@@ -66,7 +66,7 @@ RSpec.describe MigrationsHelpers, feature_category: :database do
end
it 'creates a class based on main base model' do
- klass = helper.table(:ci_builds, database: :ci)
+ klass = helper.table(:p_ci_builds, database: :ci) { |model| model.primary_key = :id }
expect(klass.connection_specification_name).to eq('ActiveRecord::Base')
end
end