Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-10-26 15:10:10 +00:00
parent c15582526d
commit c86ec1d072
55 changed files with 981 additions and 372 deletions

View File

@ -717,7 +717,6 @@ Gitlab/RailsLogger:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/267606
RSpec/FactoryBot/InlineAssociation:
Include:
- 'spec/factories/**/*.rb'

View File

@ -53,9 +53,10 @@ export const initCopyCodeButton = (selector = '#content-body') => {
customElements.define('copy-code', CopyCodeButton);
}
const exclude = document.querySelector('.file-content.code'); // this behavior is not needed when viewing raw file content, so excluding it as the unnecessary dom lookups can become expensive
const el = document.querySelector(selector);
if (!el) return () => {};
if (!el || exclude) return () => {};
const observer = new MutationObserver(() => addCodeButton());

View File

@ -1,4 +1,6 @@
function addBlameLink(containerSelector, linkClass) {
import { getPageParamValue, getPageSearchString } from './utils';
export function addBlameLink(containerSelector, linkClass) {
const containerEl = document.querySelector(containerSelector);
if (!containerEl) {
@ -13,10 +15,14 @@ function addBlameLink(containerSelector, linkClass) {
lineLinkCopy.classList.remove(linkClass, 'diff-line-num');
const { lineNumber } = lineLink.dataset;
const { blamePath } = document.querySelector('.line-numbers').dataset;
const blameLink = document.createElement('a');
const { blamePath } = document.querySelector('.line-numbers').dataset;
blameLink.classList.add('file-line-blame');
blameLink.href = `${blamePath}#L${lineNumber}`;
const blamePerPage = document.querySelector('.js-per-page')?.dataset?.blamePerPage;
const pageNumber = getPageParamValue(lineNumber, blamePerPage);
const searchString = getPageSearchString(blamePath, pageNumber);
blameLink.href = `${blamePath}${searchString}#L${lineNumber}`;
const wrapper = document.createElement('div');
wrapper.classList.add('line-links', 'diff-line-num');
@ -27,5 +33,3 @@ function addBlameLink(containerSelector, linkClass) {
}
});
}
export default addBlameLink;

View File

@ -1,4 +1,5 @@
import { getLocationHash } from '../lib/utils/url_utility';
import { getPageParamValue, getPageSearchString } from './utils';
const lineNumberRe = /^(L|LC)[0-9]+/;
@ -16,7 +17,10 @@ const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
permalinkButton.dataset.originalHref = href;
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
const lineNum = parseInt(hash.split('L')[1], 10);
const page = getPageParamValue(lineNum);
const searchString = getPageSearchString(baseHref, page);
permalinkButton.setAttribute('href', `${baseHref}${searchString}${hashUrlString}`);
});
}
};

View File

@ -1,4 +1,5 @@
import Editor from '~/editor/source_editor';
import { getBaseURL } from '~/lib/utils/url_utility';
export function initSourceEditor({ el, ...args }) {
const editor = new Editor({
@ -13,4 +14,19 @@ export function initSourceEditor({ el, ...args }) {
});
}
const blameLinesPerPage = document.querySelector('.js-per-page')?.dataset?.blamePerPage;
export const getPageParamValue = (lineNum, blamePerPage = blameLinesPerPage) => {
if (!blamePerPage) return '';
const page = Math.ceil(parseInt(lineNum, 10) / parseInt(blamePerPage, 10));
return page <= 1 ? '' : page;
};
export const getPageSearchString = (path, page) => {
if (!page) return '';
const url = new URL(path, getBaseURL());
url.searchParams.set('page', page);
return url.search;
};
export default () => ({});

View File

@ -14,7 +14,7 @@ import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import CodeIntelligence from '~/code_navigation/components/app.vue';
import LineHighlighter from '~/blob/line_highlighter';
import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql';
import addBlameLink from '~/blob/blob_blame_link';
import { addBlameLink } from '~/blob/blob_blame_link';
import projectInfoQuery from '../queries/project_info.query.graphql';
import getRefMixin from '../mixins/get_ref';
import userInfoQuery from '../queries/user_info.query.graphql';

View File

@ -15,8 +15,14 @@ export default {
mounted() {
handleBlobRichViewer(this.$refs.content, this.type);
},
safeHtmlConfig: {
ADD_TAGS: ['copy-code'],
},
};
</script>
<template>
<markdown-field-view ref="content" v-safe-html="richViewer || content" />
<markdown-field-view
ref="content"
v-safe-html:[$options.safeHtmlConfig]="richViewer || content"
/>
</template>

View File

@ -1,5 +1,6 @@
<script>
import { GlIntersectionObserver, GlSafeHtmlDirective } from '@gitlab/ui';
import { scrollToElement } from '~/lib/utils/common_utils';
import ChunkLine from './chunk_line.vue';
/*
@ -46,6 +47,11 @@ export default {
required: false,
default: 0,
},
totalChunks: {
type: Number,
required: false,
default: 0,
},
language: {
type: String,
required: false,
@ -56,11 +62,27 @@ export default {
required: true,
},
},
data() {
return {
isLoading: true,
};
},
computed: {
lines() {
return this.content.split('\n');
},
},
created() {
window.requestIdleCallback(() => {
this.isLoading = false;
const { hash } = this.$route;
if (hash && this.totalChunks > 0 && this.totalChunks === this.chunkIndex + 1) {
// when the last chunk is loaded scroll to the hash
scrollToElement(hash, { behavior: 'auto' });
}
});
},
methods: {
handleChunkAppear() {
if (!this.isHighlighted) {
@ -85,17 +107,18 @@ export default {
:blame-path="blamePath"
/>
</div>
<div v-else class="gl-display-flex gl-text-transparent">
<div v-else-if="!isLoading" class="gl-display-flex gl-text-transparent">
<div class="gl-display-flex gl-flex-direction-column content-visibility-auto">
<span
v-for="(n, index) in totalLines"
v-once
:id="`L${calculateLineNumber(index)}`"
:key="index"
data-testid="line-number"
v-text="calculateLineNumber(index)"
></span>
</div>
<div class="gl-white-space-pre-wrap!" data-testid="content" v-text="content"></div>
<div v-once class="gl-white-space-pre-wrap!" data-testid="content">{{ content }}</div>
</div>
</gl-intersection-observer>
</template>

View File

@ -1,6 +1,7 @@
<script>
import { GlSafeHtmlDirective } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
export default {
directives: {
@ -25,6 +26,13 @@ export default {
required: true,
},
},
computed: {
pageSearchString() {
if (!this.glFeatures.fileLineBlame) return '';
const page = getPageParamValue(this.number);
return getPageSearchString(this.blamePath, page);
},
},
};
</script>
<template>
@ -35,7 +43,7 @@ export default {
<a
v-if="glFeatures.fileLineBlame"
class="gl-user-select-none gl-shadow-none! file-line-blame"
:href="`${blamePath}#L${number}`"
:href="`${blamePath}${pageSearchString}#L${number}`"
></a>
<a
:id="`L${number}`"

View File

@ -65,6 +65,9 @@ export default {
!supportedLanguages.includes(this.blob.language?.toLowerCase())
);
},
totalChunks() {
return Object.keys(this.chunks).length;
},
},
async created() {
addBlobLinksTracking();
@ -217,6 +220,7 @@ export default {
:chunk-index="index"
:language="chunk.language"
:blame-path="blob.blamePath"
:total-chunks="totalChunks"
@appear="highlightChunk"
/>
</div>

View File

@ -1,46 +0,0 @@
# frozen_string_literal: true
module Packages
module Rpm
module RepositoryMetadata
class BaseBuilder
def initialize(xml: nil, data: {})
@xml = Nokogiri::XML(xml) if xml.present?
@data = data
end
def execute
return build_empty_structure if xml.blank?
update_xml_document
update_package_count
xml.to_xml
end
private
attr_reader :xml, :data
def build_empty_structure
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
xml.method_missing(self.class::ROOT_TAG, self.class::ROOT_ATTRIBUTES)
end.to_xml
end
def update_xml_document
# Add to the root xml element a new package metadata node
xml.at(self.class::ROOT_TAG).add_child(build_new_node)
end
def update_package_count
packages_count = xml.css("//#{self.class::ROOT_TAG}/package").count
xml.at(self.class::ROOT_TAG).attributes["packages"].value = packages_count.to_s
end
def build_new_node
raise NotImplementedError, "#{self.class} should implement #{__method__}"
end
end
end
end
end

View File

@ -1,14 +0,0 @@
# frozen_string_literal: true
module Packages
module Rpm
module RepositoryMetadata
class BuildFilelistXml < ::Packages::Rpm::RepositoryMetadata::BaseBuilder
ROOT_TAG = 'filelists'
ROOT_ATTRIBUTES = {
xmlns: 'http://linux.duke.edu/metadata/filelists',
packages: '0'
}.freeze
end
end
end
end

View File

@ -1,14 +0,0 @@
# frozen_string_literal: true
module Packages
module Rpm
module RepositoryMetadata
class BuildOtherXml < ::Packages::Rpm::RepositoryMetadata::BaseBuilder
ROOT_TAG = 'otherdata'
ROOT_ATTRIBUTES = {
xmlns: 'http://linux.duke.edu/metadata/other',
packages: '0'
}.freeze
end
end
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
module Packages
module Rpm
module RepositoryMetadata
class BuildOtherXmlService
ROOT_TAG = 'otherdata'
ROOT_ATTRIBUTES = {
xmlns: 'http://linux.duke.edu/metadata/other',
packages: '0'
}.freeze
def initialize(data)
@data = data
end
def execute
builder = Nokogiri::XML::Builder.new do |xml|
xml.package(pkgid: data[:pkgid], name: data[:name], arch: data[:arch]) do
xml.version epoch: data[:epoch], ver: data[:version], rel: data[:release]
build_changelog_nodes(xml)
end
end
Nokogiri::XML(builder.to_xml).at('package')
end
private
attr_reader :data
def build_changelog_nodes(xml)
data[:changelogs].each do |changelog|
xml.changelog changelog[:changelogtext], date: changelog[:changelogtime]
end
end
end
end
end
end

View File

@ -2,7 +2,7 @@
module Packages
module Rpm
module RepositoryMetadata
class BuildPrimaryXml < ::Packages::Rpm::RepositoryMetadata::BaseBuilder
class BuildPrimaryXmlService
ROOT_TAG = 'metadata'
ROOT_ATTRIBUTES = {
xmlns: 'http://linux.duke.edu/metadata/common',
@ -11,19 +11,19 @@ module Packages
}.freeze
# Nodes that have only text without attributes
REQUIRED_BASE_ATTRIBUTES = %i[name arch summary description].freeze
NOT_REQUIRED_BASE_ATTRIBUTES = %i[url packager].freeze
BASE_ATTRIBUTES = %i[name arch summary description url packager].freeze
FORMAT_NODE_BASE_ATTRIBUTES = %i[license vendor group buildhost sourcerpm].freeze
private
def initialize(data)
@data = data
end
def build_new_node
def execute
builder = Nokogiri::XML::Builder.new do |xml|
xml.package(type: :rpm, 'xmlns:rpm': 'http://linux.duke.edu/metadata/rpm') do
build_required_base_attributes(xml)
build_not_required_base_attributes(xml)
build_base_attributes(xml)
xml.version epoch: data[:epoch], ver: data[:version], rel: data[:release]
xml.checksum data[:checksum], type: 'sha256', pkgid: 'YES'
xml.checksum data[:pkgid], type: 'sha256', pkgid: 'YES'
xml.size package: data[:packagesize], installed: data[:installedsize], archive: data[:archivesize]
xml.time file: data[:filetime], build: data[:buildtime]
xml.location href: data[:location] if data[:location].present?
@ -34,14 +34,12 @@ module Packages
Nokogiri::XML(builder.to_xml).at('package')
end
def build_required_base_attributes(xml)
REQUIRED_BASE_ATTRIBUTES.each do |attribute|
xml.method_missing(attribute, data[attribute])
end
end
private
def build_not_required_base_attributes(xml)
NOT_REQUIRED_BASE_ATTRIBUTES.each do |attribute|
attr_reader :data
def build_base_attributes(xml)
BASE_ATTRIBUTES.each do |attribute|
xml.method_missing(attribute, data[attribute]) if data[attribute].present?
end
end

View File

@ -2,7 +2,7 @@
module Packages
module Rpm
module RepositoryMetadata
class BuildRepomdXml
class BuildRepomdXmlService
attr_reader :data
ROOT_ATTRIBUTES = {

View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
module Packages
module Rpm
module RepositoryMetadata
class UpdateXmlService
BUILDERS = {
other: ::Packages::Rpm::RepositoryMetadata::BuildOtherXmlService,
primary: ::Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService
}.freeze
def initialize(filename:, xml: nil, data: {})
@builder_class = BUILDERS[filename]
raise ArgumentError, "Filename must be one of: #{BUILDERS.keys.join(', ')}" if @builder_class.nil?
@xml = Nokogiri::XML(xml) if xml.present?
@data = data
@filename = filename
end
def execute
return build_empty_structure if xml.blank?
remove_existing_packages
update_xml_document
update_package_count
xml.to_xml
end
private
attr_reader :xml, :data, :builder_class, :filename
def build_empty_structure
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
xml.method_missing(builder_class::ROOT_TAG, builder_class::ROOT_ATTRIBUTES)
end.to_xml
end
def update_xml_document
# Add to the root xml element a new package metadata node
xml.at(builder_class::ROOT_TAG).add_child(builder_class.new(data).execute)
end
def update_package_count
packages_count = xml.css("//#{builder_class::ROOT_TAG}/package").count
xml.at(builder_class::ROOT_TAG).attributes["packages"].value = packages_count.to_s
end
def remove_existing_packages
case filename
when :primary
xml.search("checksum:contains('#{data[:pkgid]}')").each { _1.parent&.remove }
else
xml.search("[pkgid='#{data[:pkgid]}']").each(&:remove)
end
end
end
end
end
end

View File

@ -6,7 +6,7 @@
- if readme_path = @project.repository.readme_path
- add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
#tree-holder.tree-holder.clearfix
#tree-holder.tree-holder.clearfix.js-per-page{ data: { blame_per_page: Projects::BlameService::PER_PAGE } }
.nav-block.gl-display-flex.gl-xs-flex-direction-column.gl-align-items-stretch
= render 'projects/tree/tree_header', tree: @tree, is_project_overview: is_project_overview

View File

@ -13,7 +13,7 @@
#js-code-owners{ data: { blob_path: blob.path, project_path: @project.full_path, branch: @ref } }
= render "projects/blob/auxiliary_viewer", blob: blob
#blob-content-holder.blob-content-holder
#blob-content-holder.blob-content-holder.js-per-page{ data: { blame_per_page: Projects::BlameService::PER_PAGE } }
- if @code_navigation_path
#js-code-navigation{ data: { code_navigation_path: @code_navigation_path, blob_path: blob.path, definition_path_prefix: project_blob_path(@project, @ref) } }
- if !expanded

View File

@ -27,6 +27,7 @@ options:
- i_package_pypi_deploy_token
- i_package_rubygems_deploy_token
- i_package_terraform_module_deploy_token
- i_package_rpm_deploy_token
distribution:
- ee
- ce

View File

@ -0,0 +1,25 @@
---
data_category: optional
key_path: redis_hll_counters.user_packages.i_package_rpm_user_weekly
description: A weekly count of users that have published an rpm package to the registry
product_section: ops
product_stage: package
product_group: package
product_category: package_registry
value_type: number
status: active
milestone: '15.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
options:
events:
- i_package_rpm_user
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,25 @@
---
data_category: optional
key_path: redis_hll_counters.deploy_token_packages.i_package_rpm_deploy_token_weekly
description: A weekly count of RPM packages published to the registry using a deploy token
product_section: ops
product_stage: package
product_group: package
product_category: package_registry
value_type: number
status: active
milestone: '15.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
options:
events:
- i_package_rpm_deploy_token
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,25 @@
---
data_category: optional
key_path: counts.package_events_i_package_rpm_pull_package
description: A count of RPM packages that have been downloaded
product_section: ops
product_stage: package
product_group: package
product_category: package_registry
value_type: number
status: active
milestone: '15.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
time_frame: all
data_source: redis
instrumentation_class: RedisMetric
options:
prefix: package_events
event: i_package_rpm_pull_package
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,25 @@
---
data_category: optional
key_path: counts.package_events_i_package_rpm_push_package
description: A count of RPM packages that have been published
product_section: ops
product_stage: package
product_group: package
product_category: package_registry
value_type: number
status: active
milestone: '15.6'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
time_frame: all
data_source: redis
instrumentation_class: RedisMetric
options:
prefix: package_events
event: i_package_rpm_push_package
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -174,7 +174,7 @@ For installations from source:
RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:lfs:migrate
```
You can optionally track progress and verify that all packages migrated successfully using the
You can optionally track progress and verify that all LFS objects migrated successfully using the
[PostgreSQL console](https://docs.gitlab.com/omnibus/settings/database.html#connecting-to-the-bundled-postgresql-database):
- `sudo gitlab-rails dbconsole` for Omnibus GitLab instances.

View File

@ -179,7 +179,10 @@ To set issue weight of a task:
## Add a task to an iteration **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/362550) in GitLab 15.5.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367456) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc_2`. On GitLab.com, this feature is not available. The feature is not ready for production use.
You can add a task to an [iteration](group/iterations/index.md).
You can see the iteration title and period only when you view a task.

View File

@ -17,7 +17,7 @@ module API
requires :namespace_path, type: String, desc: 'Path for the namespace that should be subscribed'
end
post do
not_found! unless Feature.enabled?(:jira_connect_oauth_self_managed, current_user)
not_found! unless Feature.enabled?(:jira_connect_oauth, current_user)
jwt = Atlassian::JiraConnect::Jwt::Symmetric.new(params[:jwt])
installation = JiraConnectInstallation.find_by_client_key(jwt.iss_claim)

View File

@ -39,6 +39,13 @@ module API
requires :file_name, type: String, desc: 'RPM package file name'
end
get '*package_file_id/*file_name', requirements: { file_name: API::NO_SLASH_URL_PART_REGEX } do
track_package_event(
'pull_package',
:rpm,
category: self.class.name,
project: authorized_user_project,
namespace: authorized_user_project.namespace
)
not_found!
end
@ -50,6 +57,15 @@ module API
bad_request!('File is too large')
end
track_package_event(
'push_package',
:rpm,
user: current_user,
category: self.class.name,
project: authorized_user_project,
namespace: authorized_user_project.namespace
)
not_found!
end

View File

@ -976,6 +976,24 @@ module Gitlab
rescue ArgumentError
end
# Remove any instances of deprecated job classes lingering in queues.
#
# rubocop:disable Cop/SidekiqApiUsage
def sidekiq_remove_jobs(job_klass:)
Sidekiq::Queue.new(job_klass.queue).each do |job|
job.delete if job.klass == job_klass.to_s
end
Sidekiq::RetrySet.new.each do |retri|
retri.delete if retri.klass == job_klass.to_s
end
Sidekiq::ScheduledSet.new.each do |scheduled|
scheduled.delete if scheduled.klass == job_klass.to_s
end
end
# rubocop:enable Cop/SidekiqApiUsage
def sidekiq_queue_migrate(queue_from, to:)
while sidekiq_queue_length(queue_from) > 0
Sidekiq.redis do |conn|

View File

@ -10,6 +10,14 @@ module Gitlab
RECEIVED_HEADER_REGEX = /for\s+\<(.+)\>/.freeze
# Errors that are purely from users and not anything we can control
USER_ERRORS = [
Gitlab::Email::AutoGeneratedEmailError, Gitlab::Email::ProjectNotFound, Gitlab::Email::EmptyEmailError,
Gitlab::Email::UserNotFoundError, Gitlab::Email::UserBlockedError, Gitlab::Email::UserNotAuthorizedError,
Gitlab::Email::NoteableNotFoundError, Gitlab::Email::InvalidAttachment, Gitlab::Email::InvalidRecordError,
Gitlab::Email::EmailTooLarge
].freeze
def initialize(raw)
@raw = raw
end
@ -24,6 +32,9 @@ module Gitlab
handler.execute.tap do
Gitlab::Metrics::BackgroundTransaction.current&.add_event(handler.metrics_event, handler.metrics_params)
end
rescue *USER_ERRORS => e
# do not send a metric event since these are purely user errors that we can't control
raise e
rescue StandardError => e
Gitlab::Metrics::BackgroundTransaction.current&.add_event('email_receiver_error', error: e.class.name)
raise e

View File

@ -55,3 +55,5 @@
- i_package_terraform_module_delete_package
- i_package_terraform_module_pull_package
- i_package_terraform_module_push_package
- i_package_rpm_push_package
- i_package_rpm_pull_package

View File

@ -79,3 +79,11 @@
category: user_packages
aggregation: weekly
redis_slot: package
- name: i_package_rpm_user
category: user_packages
aggregation: weekly
redis_slot: package
- name: i_package_rpm_deploy_token
category: deploy_token_packages
aggregation: weekly
redis_slot: package

View File

@ -120,15 +120,47 @@ module UnnestedInFilters
# "vulnerability_reads"."vulnerability_id" DESC
# LIMIT 20
#
# If one of the columns being used for filtering or ordering is the primary key,
# then the query will be further optimized to use an index-only scan for initial filtering
# before selecting all columns using the primary key.
#
# Using the prior query as an example, where `vulnerability_id` is the primary key,
# This will be rewritten to:
#
# SELECT
# "vulnerability_reads".*
# FROM
# "vulnerability_reads"
# WHERE
# "vulnerability_reads"."vulnerability_id"
# IN (
# SELECT
# "vulnerability_reads"."vulnerability_id"
# FROM
# unnest('{1, 4}'::smallint[]) AS "states" ("state"),
# LATERAL (
# SELECT
# "vulnerability_reads"."vulnerability_id"
# FROM
# "vulnerability_reads"
# WHERE
# (vulnerability_reads."state" = "states"."state")
# ORDER BY
# "vulnerability_reads"."severity" DESC,
# "vulnerability_reads"."vulnerability_id" DESC
# LIMIT 20
# ) AS vulnerability_reads
# )
# ORDER BY
# "vulnerability_reads"."severity" DESC,
# "vulnerability_reads"."vulnerability_id" DESC
# LIMIT 20
def rewrite
log_rewrite
model.from(from)
.limit(limit_value)
.order(order_values)
.includes(relation.includes_values)
.preload(relation.preload_values)
.eager_load(relation.eager_load_values)
return filter_query unless primary_key_present?
index_only_filter_query
end
def rewrite?
@ -147,6 +179,23 @@ module UnnestedInFilters
::Gitlab::AppLogger.info(message: 'Query is being rewritten by `UnnestedInFilters`', model: model.name)
end
def filter_query
model.from(from).then { add_relation_defaults(_1) }
end
def index_only_filter_query
model.where(model.primary_key => filter_query.select(model.primary_key))
.then { add_relation_defaults(_1) }
end
def add_relation_defaults(new_relation)
new_relation.limit(limit_value)
.order(order_values)
.includes(relation.includes_values)
.preload(relation.preload_values)
.eager_load(relation.eager_load_values)
end
def from
[value_tables.map(&:to_sql) + [lateral]].join(', ')
end
@ -156,9 +205,13 @@ module UnnestedInFilters
end
def join_relation
value_tables.reduce(unscoped_relation) do |memo, tmp_table|
join_relation = value_tables.reduce(unscoped_relation) do |memo, tmp_table|
memo.where(tmp_table.as_predicate)
end
join_relation = join_relation.select(combined_attributes) if primary_key_present?
join_relation
end
def unscoped_relation
@ -194,10 +247,18 @@ module UnnestedInFilters
indices.any? do |index|
(filter_attributes - Array(index.columns)).empty? && # all the filter attributes are indexed
index.columns.last(order_attributes.length) == order_attributes && # index can be used in sorting
(index.columns - (filter_attributes + order_attributes)).empty? # there is no other columns in the index
(index.columns - combined_attributes).empty? # there is no other columns in the index
end
end
def primary_key_present?
combined_attributes.include?(model.primary_key)
end
def combined_attributes
filter_attributes + order_attributes
end
def filter_attributes
@filter_attributes ||= where_values_hash.keys
end

View File

@ -1,11 +1,20 @@
{
"files": [
"/usr/bin/hello.sh"
"/usr/bin/test",
"/usr/bin/test/hello.sh"
],
"changelogs": [
{
"changelogtext": "First build",
"changelogtime": 1662552000
},
{
"changelogtext": "Next build",
"changelogtime": 1662552123
},
{
"changelogtext": "Last build",
"changelogtime": 1662552321
}
],
"requirements": [
@ -43,5 +52,7 @@
"group": "Unspecified",
"buildhost": "localhost",
"packager": null,
"vendor": null
"vendor": null,
"pkgid": "qwe123wer234ert345",
"epoch": "1"
}

View File

@ -1,5 +1,5 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import addBlameLink from '~/blob/blob_blame_link';
import { addBlameLink } from '~/blob/blob_blame_link';
describe('Blob links', () => {
const mouseoverEvent = new MouseEvent('mouseover', {
@ -10,9 +10,10 @@ describe('Blob links', () => {
beforeEach(() => {
setHTMLFixture(`
<div id="blob-content-holder">
<div id="blob-content-holder" class="js-per-page" data-blame-per-page="1000">
<div class="line-numbers" data-blame-path="/blamePath">
<a id="L5" href="#L5" data-line-number="5" class="file-line-num js-line-links">5</a>
<a id="L1005" href="#L1005" data-line-number="1005" class="file-line-num js-line-links">1005</a>
</div>
<pre id="LC5">Line 5 content</pre>
</div>
@ -44,4 +45,11 @@ describe('Blob links', () => {
expect(lineLink).not.toBeNull();
expect(lineLink.getAttribute('href')).toBe('#L5');
});
it('adds page parameter when needed', () => {
document.querySelectorAll('.file-line-num')[1].dispatchEvent(mouseoverEvent);
const blameLink = document.querySelectorAll('.file-line-blame')[1];
expect(blameLink).not.toBeNull();
expect(blameLink.getAttribute('href')).toBe('/blamePath?page=2#L1005');
});
});

View File

@ -41,4 +41,32 @@ describe('Blob utilities', () => {
);
});
});
describe('getPageParamValue', () => {
it('returns empty string if no perPage parameter is provided', () => {
const pageParamValue = utils.getPageParamValue(5);
expect(pageParamValue).toEqual('');
});
it('returns empty string if page is equal 1', () => {
const pageParamValue = utils.getPageParamValue(1000, 1000);
expect(pageParamValue).toEqual('');
});
it('returns correct page parameter value', () => {
const pageParamValue = utils.getPageParamValue(1001, 1000);
expect(pageParamValue).toEqual(2);
});
it('accepts strings as a parameter and returns correct result', () => {
const pageParamValue = utils.getPageParamValue('1001', '1000');
expect(pageParamValue).toEqual(2);
});
});
describe('getPageSearchString', () => {
it('returns empty search string if page parameter is empty value', () => {
const path = utils.getPageSearchString('/blamePath', '');
expect(path).toEqual('');
});
it('returns correct search string if value is provided', () => {
const searchString = utils.getPageSearchString('/blamePath', 3);
expect(searchString).toEqual('?page=3');
});
});
});

View File

@ -193,16 +193,18 @@ describe('deprecatedJQueryDropdown', () => {
});
it('should not focus search input while remote task is not complete', () => {
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
expect(document.activeElement).toBeDefined();
expect(document.activeElement).not.toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
remoteCallback();
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
});
it('should focus search input after remote task is complete', () => {
remoteCallback();
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
expect(document.activeElement).toBeDefined();
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
});
it('should focus on input when opening for the second time after transition', () => {
@ -215,7 +217,8 @@ describe('deprecatedJQueryDropdown', () => {
test.dropdownButtonElement.click();
test.dropdownContainerElement.trigger('transitionend');
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
expect(document.activeElement).toBeDefined();
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
});
});
@ -225,7 +228,8 @@ describe('deprecatedJQueryDropdown', () => {
test.dropdownButtonElement.click();
test.dropdownContainerElement.trigger('transitionend');
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
expect(document.activeElement).toBeDefined();
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
});
});

View File

@ -2,6 +2,9 @@ import { GlIntersectionObserver } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
import { scrollToElement } from '~/lib/utils/common_utils';
jest.mock('~/lib/utils/common_utils');
const DEFAULT_PROPS = {
chunkIndex: 2,
@ -13,11 +16,17 @@ const DEFAULT_PROPS = {
blamePath: 'blame/file.js',
};
const hash = '#L142';
describe('Chunk component', () => {
let wrapper;
let idleCallbackSpy;
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(Chunk, { propsData: { ...DEFAULT_PROPS, ...props } });
wrapper = shallowMountExtended(Chunk, {
mocks: { $route: { hash } },
propsData: { ...DEFAULT_PROPS, ...props },
});
};
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
@ -26,6 +35,7 @@ describe('Chunk component', () => {
const findContent = () => wrapper.findByTestId('content');
beforeEach(() => {
idleCallbackSpy = jest.spyOn(window, 'requestIdleCallback').mockImplementation((fn) => fn());
createComponent();
});
@ -55,6 +65,14 @@ describe('Chunk component', () => {
expect(findChunkLines().length).toBe(0);
});
it('does not render simplified line numbers and content if browser is not in idle state', () => {
idleCallbackSpy.mockRestore();
createComponent();
expect(findLineNumbers()).toHaveLength(0);
expect(findContent().exists()).toBe(false);
});
it('renders simplified line numbers and content if isHighlighted is false', () => {
expect(findLineNumbers().length).toBe(DEFAULT_PROPS.totalLines);
@ -76,5 +94,14 @@ describe('Chunk component', () => {
blamePath: DEFAULT_PROPS.blamePath,
});
});
it('does not scroll to route hash if last chunk is not loaded', () => {
expect(scrollToElement).not.toHaveBeenCalled();
});
it('scrolls to route hash if last chunk is loaded', () => {
createComponent({ totalChunks: DEFAULT_PROPS.chunkIndex + 1 });
expect(scrollToElement).toHaveBeenCalledWith(hash, { behavior: 'auto' });
});
});
});

View File

@ -10,6 +10,7 @@ import {
EVENT_LABEL_VIEWER,
EVENT_LABEL_FALLBACK,
ROUGE_TO_HLJS_LANGUAGE_MAP,
LINES_PER_CHUNK,
} from '~/vue_shared/components/source_viewer/constants';
import waitForPromises from 'helpers/wait_for_promises';
import LineHighlighter from '~/blob/line_highlighter';
@ -133,45 +134,27 @@ describe('Source Viewer component', () => {
});
describe('rendering', () => {
it('renders the first chunk', async () => {
const firstChunk = findChunks().at(0);
it.each`
chunkIndex | chunkContent | totalChunks
${0} | ${chunk1} | ${0}
${1} | ${chunk2} | ${3}
${2} | ${chunk3Result} | ${3}
`('renders chunk $chunkIndex', ({ chunkIndex, chunkContent, totalChunks }) => {
const chunk = findChunks().at(chunkIndex);
expect(firstChunk.props('content')).toContain(chunk1);
expect(chunk.props('content')).toContain(chunkContent.trim());
expect(firstChunk.props()).toMatchObject({
totalLines: 70,
startingFrom: 0,
expect(chunk.props()).toMatchObject({
totalLines: LINES_PER_CHUNK,
startingFrom: LINES_PER_CHUNK * chunkIndex,
totalChunks,
});
});
it('renders the second chunk', async () => {
const secondChunk = findChunks().at(1);
expect(secondChunk.props('content')).toContain(chunk2.trim());
expect(secondChunk.props()).toMatchObject({
totalLines: 70,
startingFrom: 70,
});
it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
findChunks().at(0).vm.$emit('appear');
expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
});
it('renders the third chunk', async () => {
const thirdChunk = findChunks().at(2);
expect(thirdChunk.props('content')).toContain(chunk3Result.trim());
expect(chunk3Result).toEqual(chunk3.replace(/\r?\n/g, '\n'));
expect(thirdChunk.props()).toMatchObject({
totalLines: 70,
startingFrom: 140,
});
});
});
it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
findChunks().at(0).vm.$emit('appear');
expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
});
describe('LineHighlighter', () => {

View File

@ -1924,8 +1924,116 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
let(:same_queue_different_worker) do
Class.new do
include Sidekiq::Worker
sidekiq_options queue: 'test'
def self.name
'SameQueueDifferentWorkerClass'
end
end
end
let(:unrelated_worker) do
Class.new do
include Sidekiq::Worker
sidekiq_options queue: 'unrelated'
def self.name
'UnrelatedWorkerClass'
end
end
end
before do
stub_const(worker.name, worker)
stub_const(unrelated_worker.name, unrelated_worker)
stub_const(same_queue_different_worker.name, same_queue_different_worker)
end
describe '#sidekiq_remove_jobs', :clean_gitlab_redis_queues do
def clear_queues
Sidekiq::Queue.new('test').clear
Sidekiq::Queue.new('unrelated').clear
Sidekiq::RetrySet.new.clear
Sidekiq::ScheduledSet.new.clear
end
around do |example|
clear_queues
Sidekiq::Testing.disable!(&example)
clear_queues
end
it "removes all related job instances from the job class's queue" do
worker.perform_async
same_queue_different_worker.perform_async
unrelated_worker.perform_async
queue_we_care_about = Sidekiq::Queue.new(worker.queue)
unrelated_queue = Sidekiq::Queue.new(unrelated_worker.queue)
expect(queue_we_care_about.size).to eq(2)
expect(unrelated_queue.size).to eq(1)
model.sidekiq_remove_jobs(job_klass: worker)
expect(queue_we_care_about.size).to eq(1)
expect(queue_we_care_about.map(&:klass)).not_to include(worker.name)
expect(queue_we_care_about.map(&:klass)).to include(
same_queue_different_worker.name
)
expect(unrelated_queue.size).to eq(1)
end
context 'when job instances are in the scheduled set' do
it 'removes all related job instances from the scheduled set' do
worker.perform_in(1.hour)
unrelated_worker.perform_in(1.hour)
scheduled = Sidekiq::ScheduledSet.new
expect(scheduled.size).to eq(2)
expect(scheduled.map(&:klass)).to include(
worker.name,
unrelated_worker.name
)
model.sidekiq_remove_jobs(job_klass: worker)
expect(scheduled.size).to eq(1)
expect(scheduled.map(&:klass)).not_to include(worker.name)
expect(scheduled.map(&:klass)).to include(unrelated_worker.name)
end
end
context 'when job instances are in the retry set' do
include_context 'when handling retried jobs'
it 'removes all related job instances from the retry set' do
retry_in(worker, 1.hour)
retry_in(worker, 2.hours)
retry_in(worker, 3.hours)
retry_in(unrelated_worker, 4.hours)
retries = Sidekiq::RetrySet.new
expect(retries.size).to eq(4)
expect(retries.map(&:klass)).to include(
worker.name,
unrelated_worker.name
)
model.sidekiq_remove_jobs(job_klass: worker)
expect(retries.size).to eq(1)
expect(retries.map(&:klass)).not_to include(worker.name)
expect(retries.map(&:klass)).to include(unrelated_worker.name)
end
end
end
describe '#sidekiq_queue_length' do
@ -1949,7 +2057,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
end
end
describe '#migrate_sidekiq_queue' do
describe '#sidekiq_queue_migrate' do
it 'migrates jobs from one sidekiq queue to another' do
Sidekiq::Testing.disable! do
worker.perform_async('Something', [1])

View File

@ -5,11 +5,10 @@ require 'spec_helper'
RSpec.describe Gitlab::Email::Receiver do
include_context :email_shared_context
let_it_be(:project) { create(:project) }
let(:metric_transaction) { instance_double(Gitlab::Metrics::WebTransaction) }
shared_examples 'successful receive' do
let_it_be(:project) { create(:project) }
let(:handler) { double(:handler, project: project, execute: true, metrics_event: nil, metrics_params: nil) }
let(:client_id) { 'email/jake@example.com' }
@ -39,7 +38,7 @@ RSpec.describe Gitlab::Email::Receiver do
end
end
shared_examples 'failed receive' do
shared_examples 'failed receive with event' do
it 'adds metric event' do
expect(::Gitlab::Metrics::BackgroundTransaction).to receive(:current).and_return(metric_transaction)
expect(metric_transaction).to receive(:add_event).with('email_receiver_error', { error: expected_error.name })
@ -48,6 +47,14 @@ RSpec.describe Gitlab::Email::Receiver do
end
end
shared_examples 'failed receive without event' do
it 'adds metric event' do
expect(::Gitlab::Metrics::BackgroundTransaction).not_to receive(:current)
expect { receiver.execute }.to raise_error(expected_error)
end
end
context 'when the email contains a valid email address in a header' do
before do
stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.example.com")
@ -100,21 +107,21 @@ RSpec.describe Gitlab::Email::Receiver do
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, '!!!') }
let(:expected_error) { Gitlab::Email::UnknownIncomingEmail }
it_behaves_like 'failed receive'
it_behaves_like 'failed receive with event'
end
context 'when the email is blank' do
let(:email_raw) { '' }
let(:expected_error) { Gitlab::Email::EmptyEmailError }
it_behaves_like 'failed receive'
it_behaves_like 'failed receive without event'
end
context 'when the email was auto generated with Auto-Submitted header' do
let(:email_raw) { fixture_file('emails/auto_submitted.eml') }
let(:expected_error) { Gitlab::Email::AutoGeneratedEmailError }
it_behaves_like 'failed receive'
it_behaves_like 'failed receive without event'
end
context "when the email's To field is blank" do
@ -164,7 +171,48 @@ RSpec.describe Gitlab::Email::Receiver do
let(:email_raw) { fixture_file('emails/auto_reply.eml') }
let(:expected_error) { Gitlab::Email::AutoGeneratedEmailError }
it_behaves_like 'failed receive'
it_behaves_like 'failed receive without event'
end
describe 'event raising via errors' do
let(:handler) { double(:handler, project: project, execute: true, metrics_event: nil, metrics_params: nil) }
let(:email_raw) { "arbitrary text. could be anything really. we're going to raise an error anyway." }
before do
allow(receiver).to receive(:handler).and_return(handler)
allow(handler).to receive(:execute).and_raise(expected_error)
end
describe 'handling errors which do not raise events' do
where(:expected_error) do
[
Gitlab::Email::AutoGeneratedEmailError,
Gitlab::Email::ProjectNotFound,
Gitlab::Email::EmptyEmailError,
Gitlab::Email::UserNotFoundError,
Gitlab::Email::UserBlockedError,
Gitlab::Email::UserNotAuthorizedError,
Gitlab::Email::NoteableNotFoundError,
Gitlab::Email::InvalidAttachment,
Gitlab::Email::InvalidRecordError,
Gitlab::Email::EmailTooLarge
]
end
with_them do
it_behaves_like 'failed receive without event'
end
end
describe 'handling errors which do raise events' do
where(:expected_error) do
[Gitlab::Email::EmailUnparsableError, Gitlab::Email::UnknownIncomingEmail, ArgumentError, StandardError]
end
with_them do
it_behaves_like 'failed receive with event'
end
end
end
it 'requires all handlers to have a unique metric_event' do

View File

@ -208,25 +208,6 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
end
context 'retried jobs' do
let(:set_name) { 'retry' }
# Account for Sidekiq retry jitter
# https://github.com/mperham/sidekiq/blob/3575ccb44c688dd08bfbfd937696260b12c622fb/lib/sidekiq/job_retry.rb#L217
let(:schedule_jitter) { 10 }
# Try to mimic as closely as possible what Sidekiq will actually
# do to retry a job.
def retry_in(klass, time, args)
message = { 'class' => klass.name, 'args' => [args], 'retry' => true }.to_json
allow(klass).to receive(:sidekiq_retry_in_block).and_return(proc { time })
begin
Sidekiq::JobRetry.new.local(klass, message, klass.queue) { raise 'boom' }
rescue Sidekiq::JobRetry::Skip
# Sidekiq scheduled the retry
end
end
def create_jobs(include_post_receive: true)
retry_in(AuthorizedProjectsWorker, 1.hour, 0)
retry_in(AuthorizedProjectsWorker, 2.hours, 1)
@ -234,6 +215,7 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
retry_in(AuthorizedProjectsWorker, 4.hours, 3)
end
include_context 'when handling retried jobs'
it_behaves_like 'processing a set'
end
end

View File

@ -170,6 +170,51 @@ RSpec.describe UnnestedInFilters::Rewriter do
end
end
context 'when the combined attributes include the primary key' do
let(:relation) { User.where(user_type: %i(support_bot alert_bot)).order(id: :desc).limit(2) }
let(:expected_query) do
<<~SQL
SELECT
"users".*
FROM
"users"
WHERE
"users"."id" IN (
SELECT
"users"."id"
FROM
unnest('{1,2}' :: smallint []) AS "user_types"("user_type"),
LATERAL (
SELECT
"users"."user_type",
"users"."id"
FROM
"users"
WHERE
(users."user_type" = "user_types"."user_type")
ORDER BY
"users"."id" DESC
LIMIT
2
) AS users
ORDER BY
"users"."id" DESC
LIMIT
2
)
ORDER BY
"users"."id" DESC
LIMIT
2
SQL
end
it 'changes the query' do
expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
end
end
describe 'logging' do
subject(:load_reload) { rewriter.rewrite }

View File

@ -22,7 +22,7 @@ RSpec.describe API::Integrations::JiraConnect::Subscriptions do
context 'with feature flag disabled' do
before do
stub_feature_flags(jira_connect_oauth_self_managed: false)
stub_feature_flags(jira_connect_oauth: false)
end
let(:jwt) { '123' }

View File

@ -9,7 +9,8 @@ RSpec.describe API::RpmProjectPackages do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:project) { create(:project, :public) }
let_it_be(:group) { create(:group, :public) }
let_it_be_with_reload(:project) { create(:project, :public, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
@ -133,16 +134,19 @@ RSpec.describe API::RpmProjectPackages do
end
describe 'GET /api/v4/projects/:id/packages/rpm/:package_file_id/:filename' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group } }
let(:url) { "/projects/#{project.id}/packages/rpm/#{package_file_id}/#{package_name}" }
subject { get api(url), headers: headers }
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
it_behaves_like 'a job token for RPM requests'
it_behaves_like 'a deploy token for RPM requests'
it_behaves_like 'a user token for RPM requests'
end
describe 'POST /api/v4/projects/:project_id/packages/rpm' do
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, user: user } }
let(:url) { "/projects/#{project.id}/packages/rpm" }
let(:file_upload) { fixture_file_upload('spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm') }
@ -150,25 +154,25 @@ RSpec.describe API::RpmProjectPackages do
context 'with user token' do
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process rpm packages upload/download' | :not_found
'PUBLIC' | :guest | true | true | 'rejects rpm packages access' | :forbidden
'PUBLIC' | :developer | true | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :developer | false | true | 'rejects rpm packages access' | :not_found
'PUBLIC' | :guest | false | true | 'rejects rpm packages access' | :not_found
'PUBLIC' | :developer | false | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized
'PRIVATE' | :developer | true | true | 'process rpm packages upload/download' | :not_found
'PRIVATE' | :guest | true | true | 'rejects rpm packages access' | :forbidden
'PRIVATE' | :developer | true | false | 'rejects rpm packages access' | :unauthorized
'PRIVATE' | :guest | true | false | 'rejects rpm packages access' | :unauthorized
'PRIVATE' | :developer | false | true | 'rejects rpm packages access' | :not_found
'PRIVATE' | :guest | false | true | 'rejects rpm packages access' | :not_found
'PRIVATE' | :developer | false | false | 'rejects rpm packages access' | :unauthorized
'PRIVATE' | :guest | false | false | 'rejects rpm packages access' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status, :tracked) do
'PUBLIC' | :developer | true | true | 'process rpm packages upload/download' | :not_found | true
'PUBLIC' | :guest | true | true | 'rejects rpm packages access' | :forbidden | false
'PUBLIC' | :developer | true | false | 'rejects rpm packages access' | :unauthorized | false
'PUBLIC' | :guest | true | false | 'rejects rpm packages access' | :unauthorized | false
'PUBLIC' | :developer | false | true | 'rejects rpm packages access' | :not_found | false
'PUBLIC' | :guest | false | true | 'rejects rpm packages access' | :not_found | false
'PUBLIC' | :developer | false | false | 'rejects rpm packages access' | :unauthorized | false
'PUBLIC' | :guest | false | false | 'rejects rpm packages access' | :unauthorized | false
'PUBLIC' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized | false
'PRIVATE' | :developer | true | true | 'process rpm packages upload/download' | :not_found | true
'PRIVATE' | :guest | true | true | 'rejects rpm packages access' | :forbidden | false
'PRIVATE' | :developer | true | false | 'rejects rpm packages access' | :unauthorized | false
'PRIVATE' | :guest | true | false | 'rejects rpm packages access' | :unauthorized | false
'PRIVATE' | :developer | false | true | 'rejects rpm packages access' | :not_found | false
'PRIVATE' | :guest | false | true | 'rejects rpm packages access' | :not_found | false
'PRIVATE' | :developer | false | false | 'rejects rpm packages access' | :unauthorized | false
'PRIVATE' | :guest | false | false | 'rejects rpm packages access' | :unauthorized | false
'PRIVATE' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized | false
end
with_them do
@ -180,6 +184,8 @@ RSpec.describe API::RpmProjectPackages do
project.send("add_#{user_role}", user) if member && user_role != :anonymous
end
tracking_example = params[:tracked] ? 'a package tracking event' : 'not a package tracking event'
it_behaves_like tracking_example, described_class.name, 'push_package'
it_behaves_like params[:shared_examples_name], params[:expected_status]
end
end

View File

@ -56,17 +56,6 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do
end
end
context 'when an ambiguous branch name is provided' do
let(:branches) { { 'ambiguous/test' => 'master', 'ambiguous' => 'master' } }
it 'returns an error that branch could not be created' do
err_msg = 'Failed to create branch \'ambiguous\': 13:reference is ambiguous.'
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to match_array([err_msg])
end
end
context 'when PreReceiveError exception' do
let(:branches) { { 'error' => 'master' } }
@ -184,18 +173,6 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do
end
end
context 'when an ambiguous branch name is provided' do
it 'returns an error that branch could not be created' do
err_msg = 'Failed to create branch \'feature\': 13:reference is ambiguous.'
service.execute('feature/widget', 'master')
result = service.execute('feature', 'master')
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq(err_msg)
end
end
it 'logs and returns an error if there is a PreReceiveError exception' do
error_message = 'pre receive error'
raw_message = "GitLab: #{error_message}"

View File

@ -1,33 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::BaseBuilder do
describe '#execute' do
subject { described_class.new(xml: xml, data: data).execute }
let(:xml) { nil }
let(:data) { {} }
before do
stub_const("#{described_class}::ROOT_TAG", 'test')
stub_const("#{described_class}::ROOT_ATTRIBUTES", { foo1: 'bar1', foo2: 'bar2' })
end
it 'generate valid xml' do
result = Nokogiri::XML::Document.parse(subject)
expect(result.children.count).to eq(1)
expect(result.children.first.attributes.count).to eq(2)
expect(result.children.first.attributes['foo1'].value).to eq('bar1')
expect(result.children.first.attributes['foo2'].value).to eq('bar2')
end
context 'when call with parameters' do
let(:xml) { 'test' }
it 'raise NotImplementedError' do
expect { subject }.to raise_error NotImplementedError
end
end
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildFilelistXml do
describe '#execute' do
subject { described_class.new.execute }
context "when generate empty xml" do
let(:expected_xml) do
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<filelists xmlns="http://linux.duke.edu/metadata/filelists" packages="0"/>
XML
end
it 'generate expected xml' do
expect(subject).to eq(expected_xml)
end
end
end
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXmlService do
describe '#execute' do
subject { described_class.new(data).execute }
context 'when updating existing xml' do
include_context 'with rpm package data'
let(:data) { xml_update_params }
let(:changelog_xpath) { "//package/changelog" }
it 'adds all changelog nodes' do
result = subject
expect(result.xpath(changelog_xpath).count).to eq(data[:changelogs].count)
end
it 'set required date attribute' do
result = subject
data[:changelogs].each do |changelog|
expect(result.at("#{changelog_xpath}[@date=\"#{changelog[:changelogtime]}\"]")).not_to be_nil
end
end
end
end
end

View File

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXml do
describe '#execute' do
subject { described_class.new.execute }
context "when generate empty xml" do
let(:expected_xml) do
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<otherdata xmlns="http://linux.duke.edu/metadata/other" packages="0"/>
XML
end
it 'generate expected xml' do
expect(subject).to eq(expected_xml)
end
end
end
end

View File

@ -1,32 +1,22 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXml do
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService do
describe '#execute' do
subject { described_class.new(xml: xml, data: data).execute }
let(:empty_xml) do
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="0"/>
XML
end
it_behaves_like 'handling rpm xml file'
subject { described_class.new(data).execute }
context 'when updating existing xml' do
include_context 'with rpm package data'
let(:xml) { empty_xml }
let(:data) { xml_update_params }
let(:required_text_only_attributes) { %i[description summary arch name] }
it 'adds node with required_text_only_attributes' do
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
result = subject
required_text_only_attributes.each do |attribute|
expect(
result.at("//#{described_class::ROOT_TAG}/package/#{attribute}").text
result.at("//package/#{attribute}").text
).to eq(data[attribute])
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXml do
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXmlService do
describe '#execute' do
subject { described_class.new(data).execute }

View File

@ -0,0 +1,155 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService do
describe '#execute' do
subject { described_class.new(filename: filename, xml: xml, data: data).execute }
let(:xml) { nil }
let(:data) { nil }
shared_examples 'handling not implemented xml filename' do
let(:filename) { :not_implemented_yet }
let(:empty_xml) { '' }
it 'raise error' do
expect { subject }.to raise_error(ArgumentError)
end
end
shared_context 'with primary xml file data' do
let(:filename) { :primary }
let(:empty_xml) do
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="0"/>
XML
end
end
shared_context 'with other xml file data' do
let(:filename) { :other }
let(:empty_xml) do
<<~XML
<?xml version="1.0" encoding="UTF-8"?>
<otherdata xmlns="http://linux.duke.edu/metadata/other" packages="0"/>
XML
end
end
context 'when building empty xml' do
shared_examples 'generating empty xml' do
it 'generate expected xml' do
expect(subject).to eq(empty_xml)
end
end
it_behaves_like 'handling not implemented xml filename'
context "for 'primary' xml file" do
include_context 'with primary xml file data'
it_behaves_like 'generating empty xml'
end
context "for 'other' xml file" do
include_context 'with other xml file data'
it_behaves_like 'generating empty xml'
end
end
context 'when updating xml file' do
include_context 'with rpm package data'
let(:xml) { empty_xml }
let(:data) { xml_update_params }
let(:builder_class) { described_class::BUILDERS[filename] }
shared_examples 'updating rpm xml file' do
context 'when updating existing xml' do
shared_examples 'changing root tag attribute' do
it "increment previous 'packages' value by 1" do
previous_value = Nokogiri::XML(xml).at(builder_class::ROOT_TAG).attributes["packages"].value.to_i
new_value = Nokogiri::XML(subject).at(builder_class::ROOT_TAG).attributes["packages"].value.to_i
expect(previous_value + 1).to eq(new_value)
end
end
it 'generate valid xml add expected xml node to existing xml' do
# Have one root attribute
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
expect(result.children.count).to eq(1)
# Root node has 1 child with generated node
expect(result.xpath("//#{builder_class::ROOT_TAG}/package").count).to eq(1)
end
context 'when empty xml' do
it_behaves_like 'changing root tag attribute'
end
context 'when xml has children' do
context "when node with given 'pkgid' does not exist yet" do
let(:uniq_node_data) do
xml_update_params.tap do |data|
data[:pkgid] = SecureRandom.uuid
end
end
let(:xml) { build_xml_from(uniq_node_data) }
it 'has children nodes' do
existing_xml = Nokogiri::XML::Document.parse(xml).remove_namespaces!
expect(existing_xml.xpath('//package').count).to eq(1)
end
it_behaves_like 'changing root tag attribute'
end
context "when node with given 'pkgid' already exist" do
let(:existing_node_data) do
existing_data = data.dup
existing_data[:name] = FFaker::Lorem.word
existing_data
end
let(:xml) { build_xml_from(existing_node_data) }
it 'has children nodes' do
existing_xml = Nokogiri::XML::Document.parse(xml).remove_namespaces!
expect(existing_xml.xpath('//package').count).to eq(1)
end
it 'replace existing node with new data' do
existing_xml = Nokogiri::XML::Document.parse(xml).remove_namespaces!
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
expect(result.xpath('//package').count).to eq(1)
expect(result.xpath('//package').first.to_xml).not_to eq(existing_xml.xpath('//package').first.to_xml)
end
end
def build_xml_from(data)
described_class.new(filename: filename, xml: empty_xml, data: data).execute
end
end
end
end
it_behaves_like 'handling not implemented xml filename'
context "for 'primary' xml file" do
include_context 'with primary xml file data'
it_behaves_like 'updating rpm xml file'
end
context "for 'other' xml file" do
include_context 'with other xml file data'
it_behaves_like 'updating rpm xml file'
end
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
RSpec.shared_context 'when handling retried jobs' do |url|
let(:set_name) { 'retry' }
# Account for Sidekiq retry jitter
# https://github.com/mperham/sidekiq/blob/3575ccb44c688dd08bfbfd937696260b12c622fb/lib/sidekiq/job_retry.rb#L217
let(:schedule_jitter) { 10 }
# Try to mimic as closely as possible what Sidekiq will actually
# do to retry a job.
def retry_in(klass, time, args = 0)
message = Gitlab::Json.generate(
'class' => klass.name,
'args' => [args],
'retry' => true
)
allow(klass).to receive(:sidekiq_retry_in_block).and_return(proc { time })
begin
Sidekiq::JobRetry.new.local(klass, message, klass.queue) { raise 'boom' }
rescue Sidekiq::JobRetry::Skip
# Sidekiq scheduled the retry
end
end
end

View File

@ -1,52 +0,0 @@
# frozen_string_literal: true
RSpec.shared_examples 'handling rpm xml file' do
include_context 'with rpm package data'
let(:xml) { nil }
let(:data) { {} }
context 'when generate empty xml' do
it 'generate expected xml' do
expect(subject).to eq(empty_xml)
end
end
context 'when updating existing xml' do
let(:xml) { empty_xml }
let(:data) { xml_update_params }
shared_examples 'changing root tag attribute' do
it "increment previous 'packages' value by 1" do
previous_value = Nokogiri::XML(xml).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
new_value = Nokogiri::XML(subject).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
expect(previous_value + 1).to eq(new_value)
end
end
it 'generate valid xml add expected xml node to existing xml' do
# Have one root attribute
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
expect(result.children.count).to eq(1)
# Root node has 1 child with generated node
expect(result.xpath("//#{described_class::ROOT_TAG}/package").count).to eq(1)
end
context 'when empty xml' do
it_behaves_like 'changing root tag attribute'
end
context 'when xml has children' do
let(:xml) { described_class.new(xml: empty_xml, data: data).execute }
it 'has children nodes' do
result = Nokogiri::XML::Document.parse(xml).remove_namespaces!
expect(result.children.count).to be > 0
end
it_behaves_like 'changing root tag attribute'
end
end
end