Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8322f05174
commit
e0655935eb
|
@ -159,6 +159,7 @@
|
|||
/lib/gitlab/github_import/ @gitlab-org/maintainers/database
|
||||
/app/finders/ @gitlab-org/maintainers/database
|
||||
/ee/app/finders/ @gitlab-org/maintainers/database
|
||||
/rubocop/rubocop-migrations.yml @gitlab-org/maintainers/database
|
||||
|
||||
[Engineering Productivity]
|
||||
/.gitlab-ci.yml @gl-quality/eng-prod
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<script>
|
||||
import DeleteUserModal from './delete_user_modal.vue';
|
||||
|
||||
export default {
|
||||
components: { DeleteUserModal },
|
||||
props: {
|
||||
modalConfiguration: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
actionModals: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
csrfToken: {
|
||||
required: true,
|
||||
type: String,
|
||||
|
@ -21,10 +20,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
activeModal() {
|
||||
if (!this.currentModalData) return null;
|
||||
const { glModalAction: action } = this.currentModalData;
|
||||
|
||||
return this.actionModals[action];
|
||||
return Boolean(this.currentModalData);
|
||||
},
|
||||
|
||||
modalProps() {
|
||||
|
@ -56,9 +52,7 @@ export default {
|
|||
|
||||
show(modalData) {
|
||||
const { glModalAction: requestedAction } = modalData;
|
||||
if (!this.actionModals[requestedAction]) {
|
||||
throw new Error(`Requested non-existing modal action ${requestedAction}`);
|
||||
}
|
||||
|
||||
if (!this.modalConfiguration[requestedAction]) {
|
||||
throw new Error(`Modal action ${requestedAction} has no configuration in HTML`);
|
||||
}
|
||||
|
@ -73,5 +67,5 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :is="activeModal" v-if="activeModal" ref="modal" v-bind="modalProps" />
|
||||
<delete-user-modal v-if="activeModal" ref="modal" v-bind="modalProps" />
|
||||
</template>
|
||||
|
|
|
@ -2,16 +2,11 @@ import Vue from 'vue';
|
|||
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import ModalManager from './components/user_modal_manager.vue';
|
||||
import DeleteUserModal from './components/delete_user_modal.vue';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import initConfirmModal from '~/confirm_modal';
|
||||
|
||||
const MODAL_TEXTS_CONTAINER_SELECTOR = '#modal-texts';
|
||||
const MODAL_MANAGER_SELECTOR = '#user-modal';
|
||||
const ACTION_MODALS = {
|
||||
delete: DeleteUserModal,
|
||||
'delete-with-contributions': DeleteUserModal,
|
||||
};
|
||||
const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts';
|
||||
const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal';
|
||||
|
||||
function loadModalsConfigurationFromHtml(modalsElement) {
|
||||
const modalsConfiguration = {};
|
||||
|
@ -54,7 +49,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
ref: 'manager',
|
||||
props: {
|
||||
modalConfiguration,
|
||||
actionModals: ACTION_MODALS,
|
||||
csrfToken: csrf.token,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
import { mapState, mapActions } from 'vuex';
|
||||
import {
|
||||
GlDrawer,
|
||||
GlBadge,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlInfiniteScroll,
|
||||
GlResizeObserverDirective,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
GlBadge,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import SkeletonLoader from './skeleton_loader.vue';
|
||||
import Feature from './feature.vue';
|
||||
import Tracking from '~/tracking';
|
||||
import { getDrawerBodyHeight } from '../utils/get_drawer_body_height';
|
||||
|
||||
|
@ -19,13 +17,11 @@ const trackingMixin = Tracking.mixin();
|
|||
export default {
|
||||
components: {
|
||||
GlDrawer,
|
||||
GlInfiniteScroll,
|
||||
GlTabs,
|
||||
GlTab,
|
||||
SkeletonLoader,
|
||||
Feature,
|
||||
GlBadge,
|
||||
GlLoadingIcon,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlInfiniteScroll,
|
||||
SkeletonLoader,
|
||||
},
|
||||
directives: {
|
||||
GlResizeObserver: GlResizeObserverDirective,
|
||||
|
@ -35,19 +31,11 @@ export default {
|
|||
storageKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
gitlabDotCom: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['open', 'features', 'pageInfo', 'drawerBodyHeight', 'fetching']),
|
||||
...mapState(['open', 'features', 'pageInfo', 'drawerBodyHeight']),
|
||||
},
|
||||
mounted() {
|
||||
this.openDrawer(this.storageKey);
|
||||
|
@ -61,25 +49,14 @@ export default {
|
|||
methods: {
|
||||
...mapActions(['openDrawer', 'closeDrawer', 'fetchItems', 'setDrawerBodyHeight']),
|
||||
bottomReached() {
|
||||
const page = this.pageInfo.nextPage;
|
||||
if (page) {
|
||||
this.fetchItems({ page });
|
||||
if (this.pageInfo.nextPage) {
|
||||
this.fetchItems(this.pageInfo.nextPage);
|
||||
}
|
||||
},
|
||||
handleResize() {
|
||||
const height = getDrawerBodyHeight(this.$refs.drawer.$el);
|
||||
this.setDrawerBodyHeight(height);
|
||||
},
|
||||
featuresForVersion(version) {
|
||||
return this.features.filter(feature => {
|
||||
return feature.release === parseFloat(version);
|
||||
});
|
||||
},
|
||||
fetchVersion(version) {
|
||||
if (this.featuresForVersion(version).length === 0) {
|
||||
this.fetchItems({ version });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -96,39 +73,64 @@ export default {
|
|||
<template #header>
|
||||
<h4 class="page-title gl-my-2">{{ __("What's new at GitLab") }}</h4>
|
||||
</template>
|
||||
<template v-if="features.length">
|
||||
<gl-infinite-scroll
|
||||
v-if="gitlabDotCom"
|
||||
:fetched-items="features.length"
|
||||
:max-list-height="drawerBodyHeight"
|
||||
class="gl-p-0"
|
||||
@bottomReached="bottomReached"
|
||||
>
|
||||
<template #items>
|
||||
<feature v-for="feature in features" :key="feature.title" :feature="feature" />
|
||||
</template>
|
||||
</gl-infinite-scroll>
|
||||
<gl-tabs v-else :style="{ height: `${drawerBodyHeight}px` }" class="gl-p-0">
|
||||
<gl-tab
|
||||
v-for="(version, index) in versions"
|
||||
:key="version"
|
||||
@click="fetchVersion(version)"
|
||||
<gl-infinite-scroll
|
||||
v-if="features.length"
|
||||
:fetched-items="features.length"
|
||||
:max-list-height="drawerBodyHeight"
|
||||
class="gl-p-0"
|
||||
@bottomReached="bottomReached"
|
||||
>
|
||||
<template #items>
|
||||
<div
|
||||
v-for="feature in features"
|
||||
:key="feature.title"
|
||||
class="gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
|
||||
>
|
||||
<template #title>
|
||||
<span>{{ version }}</span>
|
||||
<gl-badge v-if="index === 0">{{ __('Your Version') }}</gl-badge>
|
||||
</template>
|
||||
<gl-loading-icon v-if="fetching" size="lg" class="text-center" />
|
||||
<template v-else>
|
||||
<feature
|
||||
v-for="feature in featuresForVersion(version)"
|
||||
:key="feature.title"
|
||||
:feature="feature"
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
class="whats-new-item-title-link"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<h5 class="gl-font-lg">{{ feature.title }}</h5>
|
||||
</gl-link>
|
||||
<div v-if="feature.packages" class="gl-mb-3">
|
||||
<gl-badge
|
||||
v-for="package_name in feature.packages"
|
||||
:key="package_name"
|
||||
size="sm"
|
||||
class="whats-new-item-badge gl-mr-2"
|
||||
>
|
||||
<gl-icon name="license" />{{ package_name }}
|
||||
</gl-badge>
|
||||
</div>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<img
|
||||
:alt="feature.title"
|
||||
:src="feature.image_url"
|
||||
class="img-thumbnail gl-px-8 gl-py-3 whats-new-item-image"
|
||||
/>
|
||||
</template>
|
||||
</gl-tab>
|
||||
</gl-tabs>
|
||||
</template>
|
||||
</gl-link>
|
||||
<p class="gl-pt-3">{{ feature.body }}</p>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>{{ __('Learn more') }}</gl-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</gl-infinite-scroll>
|
||||
<div v-else class="gl-mt-5">
|
||||
<skeleton-loader />
|
||||
<skeleton-loader />
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
<script>
|
||||
import { GlBadge, GlIcon, GlLink } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlBadge,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
},
|
||||
props: {
|
||||
feature: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-pb-7 gl-pt-5 gl-px-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
class="whats-new-item-title-link"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<h5 class="gl-font-lg" data-test-id="feature-title">{{ feature.title }}</h5>
|
||||
</gl-link>
|
||||
<div v-if="feature.packages" class="gl-mb-3">
|
||||
<gl-badge
|
||||
v-for="packageName in feature.packages"
|
||||
:key="packageName"
|
||||
size="sm"
|
||||
class="whats-new-item-badge gl-mr-2"
|
||||
>
|
||||
<gl-icon name="license" />{{ packageName }}
|
||||
</gl-badge>
|
||||
</div>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>
|
||||
<img
|
||||
:alt="feature.title"
|
||||
:src="feature.image_url"
|
||||
class="img-thumbnail gl-px-8 gl-py-3 whats-new-item-image"
|
||||
/>
|
||||
</gl-link>
|
||||
<p class="gl-pt-3">{{ feature.body }}</p>
|
||||
<gl-link
|
||||
:href="feature.url"
|
||||
target="_blank"
|
||||
data-track-event="click_whats_new_item"
|
||||
:data-track-label="feature.title"
|
||||
:data-track-property="feature.url"
|
||||
>{{ __('Learn more') }}</gl-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
|
@ -10,6 +10,8 @@ export default el => {
|
|||
if (whatsNewApp) {
|
||||
store.dispatch('openDrawer');
|
||||
} else {
|
||||
const storageKey = getStorageKey(el);
|
||||
|
||||
whatsNewApp = new Vue({
|
||||
el,
|
||||
store,
|
||||
|
@ -26,11 +28,7 @@ export default el => {
|
|||
},
|
||||
render(createElement) {
|
||||
return createElement('app', {
|
||||
props: {
|
||||
storageKey: getStorageKey(el),
|
||||
versions: JSON.parse(el.getAttribute('data-versions')),
|
||||
gitlabDotCom: el.getAttribute('data-gitlab-dot-com'),
|
||||
},
|
||||
props: { storageKey },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
localStorage.setItem(storageKey, JSON.stringify(false));
|
||||
}
|
||||
},
|
||||
fetchItems({ commit, state }, { page, version } = { page: null, version: null }) {
|
||||
fetchItems({ commit, state }, page) {
|
||||
if (state.fetching) {
|
||||
return false;
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ export default {
|
|||
.get('/-/whats_new', {
|
||||
params: {
|
||||
page,
|
||||
version,
|
||||
},
|
||||
})
|
||||
.then(({ data, headers }) => {
|
||||
|
|
|
@ -6,32 +6,6 @@
|
|||
.gl-infinite-scroll-legend {
|
||||
@include gl-display-none;
|
||||
}
|
||||
|
||||
.gl-tabs {
|
||||
@include gl-overflow-y-auto;
|
||||
}
|
||||
|
||||
.gl-tabs-nav {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: scroll;
|
||||
align-items: stretch;
|
||||
|
||||
.nav-item {
|
||||
@include gl-flex-shrink-0;
|
||||
|
||||
a {
|
||||
@include gl-h-full;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gl-spinner-container {
|
||||
@include gl-w-full;
|
||||
@include gl-absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.with-performance-bar .whats-new-drawer {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class WhatsNewController < ApplicationController
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
before_action :check_feature_flag
|
||||
before_action :check_valid_page_param, :set_pagination_headers, unless: -> { has_version_param? }
|
||||
before_action :check_feature_flag, :check_valid_page_param, :set_pagination_headers
|
||||
|
||||
feature_category :navigation
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render json: highlight_items
|
||||
render json: most_recent_items
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -32,25 +29,15 @@ class WhatsNewController < ApplicationController
|
|||
params[:page]&.to_i || 1
|
||||
end
|
||||
|
||||
def highlights
|
||||
strong_memoize(:highlights) do
|
||||
if has_version_param?
|
||||
ReleaseHighlight.for_version(version: params[:version])
|
||||
else
|
||||
ReleaseHighlight.paginated(page: current_page)
|
||||
end
|
||||
end
|
||||
def most_recent
|
||||
@most_recent ||= ReleaseHighlight.paginated(page: current_page)
|
||||
end
|
||||
|
||||
def highlight_items
|
||||
highlights.map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) }
|
||||
def most_recent_items
|
||||
most_recent[:items].map {|item| Gitlab::WhatsNew::ItemPresenter.present(item) }
|
||||
end
|
||||
|
||||
def set_pagination_headers
|
||||
response.set_header('X-Next-Page', highlights.next_page)
|
||||
end
|
||||
|
||||
def has_version_param?
|
||||
params[:version].present?
|
||||
response.set_header('X-Next-Page', most_recent[:next_page])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,14 +6,10 @@ module WhatsNewHelper
|
|||
end
|
||||
|
||||
def whats_new_storage_key
|
||||
most_recent_version = ReleaseHighlight.versions&.first
|
||||
most_recent_version = ReleaseHighlight.most_recent_version
|
||||
|
||||
return unless most_recent_version
|
||||
|
||||
['display-whats-new-notification', most_recent_version].join('-')
|
||||
end
|
||||
|
||||
def whats_new_versions
|
||||
ReleaseHighlight.versions
|
||||
end
|
||||
end
|
||||
|
|
|
@ -916,8 +916,20 @@ module Ci
|
|||
end
|
||||
|
||||
def collect_coverage_reports!(coverage_report)
|
||||
project_path, worktree_paths = if Feature.enabled?(:smart_cobertura_parser, project)
|
||||
# If the flag is disabled, we intentionally pass nil
|
||||
# for both project_path and worktree_paths to fallback
|
||||
# to the non-smart behavior of the parser
|
||||
[project.full_path, pipeline.all_worktree_paths]
|
||||
end
|
||||
|
||||
each_report(Ci::JobArtifact::COVERAGE_REPORT_FILE_TYPES) do |file_type, blob|
|
||||
Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, coverage_report)
|
||||
Gitlab::Ci::Parsers.fabricate!(file_type).parse!(
|
||||
blob,
|
||||
coverage_report,
|
||||
project_path: project_path,
|
||||
worktree_paths: worktree_paths
|
||||
)
|
||||
end
|
||||
|
||||
coverage_report
|
||||
|
|
|
@ -831,9 +831,8 @@ module Ci
|
|||
end
|
||||
|
||||
def execute_hooks
|
||||
data = pipeline_data
|
||||
project.execute_hooks(data, :pipeline_hooks)
|
||||
project.execute_services(data, :pipeline_hooks)
|
||||
project.execute_hooks(pipeline_data, :pipeline_hooks) if project.has_active_hooks?(:pipeline_hooks)
|
||||
project.execute_services(pipeline_data, :pipeline_hooks) if project.has_active_services?(:pipeline_hooks)
|
||||
end
|
||||
|
||||
# All the merge requests for which the current pipeline runs/ran against
|
||||
|
@ -973,7 +972,7 @@ module Ci
|
|||
|
||||
def coverage_reports
|
||||
Gitlab::Ci::Reports::CoverageReports.new.tap do |coverage_reports|
|
||||
latest_report_builds(Ci::JobArtifact.coverage_reports).each do |build|
|
||||
latest_report_builds(Ci::JobArtifact.coverage_reports).includes(:project).find_each do |build|
|
||||
build.collect_coverage_reports!(coverage_reports)
|
||||
end
|
||||
end
|
||||
|
@ -1159,7 +1158,9 @@ module Ci
|
|||
end
|
||||
|
||||
def pipeline_data
|
||||
Gitlab::DataBuilder::Pipeline.build(self)
|
||||
strong_memoize(:pipeline_data) do
|
||||
Gitlab::DataBuilder::Pipeline.build(self)
|
||||
end
|
||||
end
|
||||
|
||||
def merge_request_diff_sha
|
||||
|
|
|
@ -3,17 +3,6 @@
|
|||
class ReleaseHighlight
|
||||
CACHE_DURATION = 1.hour
|
||||
FILES_PATH = Rails.root.join('data', 'whats_new', '*.yml')
|
||||
RELEASE_VERSIONS_IN_A_YEAR = 12
|
||||
|
||||
def self.for_version(version:)
|
||||
index = self.versions.index(version)
|
||||
|
||||
return if index.nil?
|
||||
|
||||
page = index + 1
|
||||
|
||||
self.paginated(page: page)
|
||||
end
|
||||
|
||||
def self.paginated(page: 1)
|
||||
Rails.cache.fetch(cache_key(page), expires_in: CACHE_DURATION) do
|
||||
|
@ -21,7 +10,10 @@ class ReleaseHighlight
|
|||
|
||||
next if items.nil?
|
||||
|
||||
QueryResult.new(items: items, next_page: next_page(current_page: page))
|
||||
{
|
||||
items: items,
|
||||
next_page: next_page(current_page: page)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -61,25 +53,15 @@ class ReleaseHighlight
|
|||
next_page if self.file_paths[next_index]
|
||||
end
|
||||
|
||||
def self.most_recent_version
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:release_version', expires_in: CACHE_DURATION) do
|
||||
self.paginated&.[](:items)&.first&.[]('release')
|
||||
end
|
||||
end
|
||||
|
||||
def self.most_recent_item_count
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:recent_item_count', expires_in: CACHE_DURATION) do
|
||||
self.paginated&.items&.count
|
||||
self.paginated&.[](:items)&.count
|
||||
end
|
||||
end
|
||||
|
||||
def self.versions
|
||||
Gitlab::ProcessMemoryCache.cache_backend.fetch('release_highlight:versions', expires_in: CACHE_DURATION) do
|
||||
versions = self.file_paths.first(RELEASE_VERSIONS_IN_A_YEAR).map do |path|
|
||||
/\d*\_(\d*\_\d*)\.yml$/.match(path).captures[0].gsub(/0(?=\d)/, "").tr("_", ".")
|
||||
end
|
||||
|
||||
versions.uniq
|
||||
end
|
||||
end
|
||||
|
||||
QueryResult = Struct.new(:items, :next_page, keyword_init: true) do
|
||||
include Enumerable
|
||||
|
||||
delegate :each, to: :items
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ module Pages
|
|||
# TODO: just remove this method after testing this in production
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/282464
|
||||
def try_obtain_lease
|
||||
return yield unless Feature.enabled?(:pages_use_legacy_storage_lease, project)
|
||||
return yield unless Feature.enabled?(:pages_use_legacy_storage_lease, project, default_enabled: true)
|
||||
|
||||
super
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#user-modal
|
||||
#modal-texts.hidden{ "hidden": true, "aria-hidden": true }
|
||||
#js-delete-user-modal
|
||||
#js-modal-texts.hidden{ "hidden": true, "aria-hidden": true }
|
||||
%div{ data: { modal: "delete",
|
||||
title: s_("AdminUsers|Delete User %{username}?"),
|
||||
action: s_('AdminUsers|Delete user'),
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
|
||||
|
||||
- if ::Feature.enabled?(:whats_new_drawer, current_user)
|
||||
#whats-new-app{ data: { storage_key: whats_new_storage_key, versions: whats_new_versions, gitlab_dot_com: Gitlab.dev_env_org_or_com? } }
|
||||
#whats-new-app{ data: { storage_key: whats_new_storage_key } }
|
||||
|
||||
- if can?(current_user, :update_user_status, current_user)
|
||||
.js-set-status-modal-wrapper{ data: user_status_data }
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Epic Board Position model to store relative positioning of epics on a board
|
||||
merge_request: 48120
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Implement smart cobertura class path correction
|
||||
merge_request: 48048
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add a job to the DAST template that shows an error in the console if the user is not licensed to use DAST.
|
||||
merge_request: 47484
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Reduce SQL queries when no pipeline hooks are active
|
||||
merge_request: 49186
|
||||
author:
|
||||
type: performance
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/282464
|
|||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::release
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: smart_cobertura_parser
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48048
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/284822
|
||||
milestone: '13.7'
|
||||
type: development
|
||||
group: group::testing
|
||||
default_enabled: false
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddEpicBoardPositions < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
create_table :boards_epic_board_positions do |t|
|
||||
t.references :epic_board, foreign_key: { to_table: :boards_epic_boards, on_delete: :cascade }, null: false, index: false
|
||||
t.references :epic, foreign_key: { on_delete: :cascade }, null: false, index: true
|
||||
t.integer :relative_position
|
||||
|
||||
t.timestamps_with_timezone null: false
|
||||
|
||||
t.index [:epic_board_id, :epic_id], unique: true, name: :index_boards_epic_board_positions_on_epic_board_id_and_epic_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
drop_table :boards_epic_board_positions
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
779effb1db70aa8b9a24942ec3e0681064c01b69ee4731f82477c54361a670b0
|
|
@ -9864,6 +9864,24 @@ CREATE SEQUENCE boards_epic_board_labels_id_seq
|
|||
|
||||
ALTER SEQUENCE boards_epic_board_labels_id_seq OWNED BY boards_epic_board_labels.id;
|
||||
|
||||
CREATE TABLE boards_epic_board_positions (
|
||||
id bigint NOT NULL,
|
||||
epic_board_id bigint NOT NULL,
|
||||
epic_id bigint NOT NULL,
|
||||
relative_position integer,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE boards_epic_board_positions_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE boards_epic_board_positions_id_seq OWNED BY boards_epic_board_positions.id;
|
||||
|
||||
CREATE TABLE boards_epic_boards (
|
||||
id bigint NOT NULL,
|
||||
hide_backlog_list boolean DEFAULT false NOT NULL,
|
||||
|
@ -17920,6 +17938,8 @@ ALTER TABLE ONLY boards ALTER COLUMN id SET DEFAULT nextval('boards_id_seq'::reg
|
|||
|
||||
ALTER TABLE ONLY boards_epic_board_labels ALTER COLUMN id SET DEFAULT nextval('boards_epic_board_labels_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY boards_epic_board_positions ALTER COLUMN id SET DEFAULT nextval('boards_epic_board_positions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY boards_epic_boards ALTER COLUMN id SET DEFAULT nextval('boards_epic_boards_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY boards_epic_user_preferences ALTER COLUMN id SET DEFAULT nextval('boards_epic_user_preferences_id_seq'::regclass);
|
||||
|
@ -18953,6 +18973,9 @@ ALTER TABLE ONLY board_user_preferences
|
|||
ALTER TABLE ONLY boards_epic_board_labels
|
||||
ADD CONSTRAINT boards_epic_board_labels_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY boards_epic_board_positions
|
||||
ADD CONSTRAINT boards_epic_board_positions_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY boards_epic_boards
|
||||
ADD CONSTRAINT boards_epic_boards_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -20582,6 +20605,10 @@ CREATE INDEX index_boards_epic_board_labels_on_epic_board_id ON boards_epic_boar
|
|||
|
||||
CREATE INDEX index_boards_epic_board_labels_on_label_id ON boards_epic_board_labels USING btree (label_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_boards_epic_board_positions_on_epic_board_id_and_epic_id ON boards_epic_board_positions USING btree (epic_board_id, epic_id);
|
||||
|
||||
CREATE INDEX index_boards_epic_board_positions_on_epic_id ON boards_epic_board_positions USING btree (epic_id);
|
||||
|
||||
CREATE INDEX index_boards_epic_boards_on_group_id ON boards_epic_boards USING btree (group_id);
|
||||
|
||||
CREATE INDEX index_boards_epic_user_preferences_on_board_id ON boards_epic_user_preferences USING btree (board_id);
|
||||
|
@ -23844,6 +23871,9 @@ ALTER TABLE ONLY approver_groups
|
|||
ALTER TABLE ONLY packages_tags
|
||||
ADD CONSTRAINT fk_rails_1dfc868911 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY boards_epic_board_positions
|
||||
ADD CONSTRAINT fk_rails_1ecfd9f2de FOREIGN KEY (epic_id) REFERENCES epics(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY geo_repository_created_events
|
||||
ADD CONSTRAINT fk_rails_1f49e46a61 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
@ -24771,6 +24801,9 @@ ALTER TABLE ONLY gpg_signatures
|
|||
ALTER TABLE ONLY board_group_recent_visits
|
||||
ADD CONSTRAINT fk_rails_ca04c38720 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY boards_epic_board_positions
|
||||
ADD CONSTRAINT fk_rails_cb4563dd6e FOREIGN KEY (epic_board_id) REFERENCES boards_epic_boards(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY vulnerability_finding_links
|
||||
ADD CONSTRAINT fk_rails_cbdfde27ce FOREIGN KEY (vulnerability_occurrence_id) REFERENCES vulnerability_occurrences(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ module API
|
|||
mount ::API::ProjectPackages
|
||||
mount ::API::GroupPackages
|
||||
mount ::API::PackageFiles
|
||||
mount ::API::NugetPackages
|
||||
mount ::API::NugetProjectPackages
|
||||
mount ::API::PypiPackages
|
||||
mount ::API::ComposerPackages
|
||||
mount ::API::ConanProjectPackages
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
# NuGet Package Manager Client API
|
||||
#
|
||||
# These API endpoints are not consumed directly by users, so there is no documentation for the
|
||||
# individual endpoints. They are called by the NuGet package manager client when users run commands
|
||||
# like `nuget install` or `nuget push`. The usage of the GitLab NuGet registry is documented here:
|
||||
# https://docs.gitlab.com/ee/user/packages/nuget_repository/
|
||||
#
|
||||
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
|
||||
module API
|
||||
module Concerns
|
||||
module Packages
|
||||
module NugetEndpoints
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze
|
||||
NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze
|
||||
|
||||
included do
|
||||
helpers do
|
||||
def find_packages
|
||||
packages = package_finder.execute
|
||||
|
||||
not_found!('Packages') unless packages.exists?
|
||||
|
||||
packages
|
||||
end
|
||||
|
||||
def find_package
|
||||
package = package_finder(package_version: params[:package_version]).execute
|
||||
.first
|
||||
|
||||
not_found!('Package') unless package
|
||||
|
||||
package
|
||||
end
|
||||
|
||||
def package_finder(finder_params = {})
|
||||
::Packages::Nuget::PackageFinder.new(
|
||||
authorized_user_project,
|
||||
**finder_params.merge(package_name: params[:package_name])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/service-index
|
||||
desc 'The NuGet Service Index' do
|
||||
detail 'This feature was introduced in GitLab 12.6'
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get 'index', format: :json do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
track_package_event('cli_metadata', :nuget, category: 'API::NugetPackages')
|
||||
|
||||
present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project),
|
||||
with: ::API::Entities::Nuget::ServiceIndex
|
||||
end
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource
|
||||
params do
|
||||
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
|
||||
end
|
||||
namespace '/metadata/*package_name' do
|
||||
before do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
end
|
||||
|
||||
desc 'The NuGet Metadata Service - Package name level' do
|
||||
detail 'This feature was introduced in GitLab 12.8'
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get 'index', format: :json do
|
||||
present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages),
|
||||
with: ::API::Entities::Nuget::PackagesMetadata
|
||||
end
|
||||
|
||||
desc 'The NuGet Metadata Service - Package name and version level' do
|
||||
detail 'This feature was introduced in GitLab 12.8'
|
||||
end
|
||||
params do
|
||||
requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get '*package_version', format: :json do
|
||||
present ::Packages::Nuget::PackageMetadataPresenter.new(find_package),
|
||||
with: ::API::Entities::Nuget::PackageMetadata
|
||||
end
|
||||
end
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
|
||||
params do
|
||||
requires :q, type: String, desc: 'The search term'
|
||||
optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX
|
||||
optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX
|
||||
optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true
|
||||
end
|
||||
namespace '/query' do
|
||||
before do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
end
|
||||
|
||||
desc 'The NuGet Search Service' do
|
||||
detail 'This feature was introduced in GitLab 12.8'
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get format: :json do
|
||||
search_options = {
|
||||
include_prerelease_versions: params[:prerelease],
|
||||
per_page: params[:take],
|
||||
padding: params[:skip]
|
||||
}
|
||||
search = ::Packages::Nuget::SearchService
|
||||
.new(authorized_user_project, params[:q], search_options)
|
||||
.execute
|
||||
|
||||
track_package_event('search_package', :nuget, category: 'API::NugetPackages')
|
||||
|
||||
present ::Packages::Nuget::SearchResultsPresenter.new(search),
|
||||
with: ::API::Entities::Nuget::SearchResults
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,15 +6,12 @@
|
|||
# called by the NuGet package manager client when users run commands
|
||||
# like `nuget install` or `nuget push`.
|
||||
module API
|
||||
class NugetPackages < ::API::Base
|
||||
class NugetProjectPackages < ::API::Base
|
||||
helpers ::API::Helpers::PackagesManagerClientsHelpers
|
||||
helpers ::API::Helpers::Packages::BasicAuthHelpers
|
||||
|
||||
feature_category :package_registry
|
||||
|
||||
POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze
|
||||
NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze
|
||||
|
||||
PACKAGE_FILENAME = 'package.nupkg'
|
||||
|
||||
default_format :json
|
||||
|
@ -23,38 +20,12 @@ module API
|
|||
render_api_error!(e.message, 400)
|
||||
end
|
||||
|
||||
helpers do
|
||||
def find_packages
|
||||
packages = package_finder.execute
|
||||
|
||||
not_found!('Packages') unless packages.exists?
|
||||
|
||||
packages
|
||||
end
|
||||
|
||||
def find_package
|
||||
package = package_finder(package_version: params[:package_version]).execute
|
||||
.first
|
||||
|
||||
not_found!('Package') unless package
|
||||
|
||||
package
|
||||
end
|
||||
|
||||
def package_finder(finder_params = {})
|
||||
::Packages::Nuget::PackageFinder.new(
|
||||
authorized_user_project,
|
||||
**finder_params.merge(package_name: params[:package_name])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
require_packages_enabled!
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, type: String, desc: 'The ID of a project', regexp: POSITIVE_INTEGER_REGEX
|
||||
requires :id, type: String, desc: 'The ID of a project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
@ -65,21 +36,7 @@ module API
|
|||
end
|
||||
|
||||
namespace ':id/packages/nuget' do
|
||||
# https://docs.microsoft.com/en-us/nuget/api/service-index
|
||||
desc 'The NuGet Service Index' do
|
||||
detail 'This feature was introduced in GitLab 12.6'
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get 'index', format: :json do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
|
||||
track_package_event('cli_metadata', :nuget)
|
||||
|
||||
present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project),
|
||||
with: ::API::Entities::Nuget::ServiceIndex
|
||||
end
|
||||
include ::API::Concerns::Packages::NugetEndpoints
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/package-publish-resource
|
||||
desc 'The NuGet Package Publish endpoint' do
|
||||
|
@ -112,7 +69,7 @@ module API
|
|||
file_params.merge(build: current_authenticated_job)
|
||||
).execute
|
||||
|
||||
track_package_event('push_package', :nuget)
|
||||
track_package_event('push_package', :nuget, category: 'API::NugetPackages')
|
||||
|
||||
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
|
||||
|
||||
|
@ -133,41 +90,6 @@ module API
|
|||
)
|
||||
end
|
||||
|
||||
params do
|
||||
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
|
||||
end
|
||||
namespace '/metadata/*package_name' do
|
||||
before do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
end
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource
|
||||
desc 'The NuGet Metadata Service - Package name level' do
|
||||
detail 'This feature was introduced in GitLab 12.8'
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get 'index', format: :json do
|
||||
present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages),
|
||||
with: ::API::Entities::Nuget::PackagesMetadata
|
||||
end
|
||||
|
||||
desc 'The NuGet Metadata Service - Package name and version level' do
|
||||
detail 'This feature was introduced in GitLab 12.8'
|
||||
end
|
||||
params do
|
||||
requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get '*package_version', format: :json do
|
||||
present ::Packages::Nuget::PackageMetadataPresenter.new(find_package),
|
||||
with: ::API::Entities::Nuget::PackageMetadata
|
||||
end
|
||||
end
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
|
||||
params do
|
||||
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX
|
||||
|
@ -205,47 +127,12 @@ module API
|
|||
|
||||
not_found!('Package') unless package_file
|
||||
|
||||
track_package_event('pull_package', :nuget)
|
||||
track_package_event('pull_package', :nuget, category: 'API::NugetPackages')
|
||||
|
||||
# nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false
|
||||
present_carrierwave_file!(package_file.file, supports_direct_download: false)
|
||||
end
|
||||
end
|
||||
|
||||
params do
|
||||
requires :q, type: String, desc: 'The search term'
|
||||
optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX
|
||||
optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX
|
||||
optional :prerelease, type: Boolean, desc: 'Include prerelease versions', default: true
|
||||
end
|
||||
namespace '/query' do
|
||||
before do
|
||||
authorize_read_package!(authorized_user_project)
|
||||
end
|
||||
|
||||
# https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
|
||||
desc 'The NuGet Search Service' do
|
||||
detail 'This feature was introduced in GitLab 12.8'
|
||||
end
|
||||
|
||||
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
|
||||
|
||||
get format: :json do
|
||||
search_options = {
|
||||
include_prerelease_versions: params[:prerelease],
|
||||
per_page: params[:take],
|
||||
padding: params[:skip]
|
||||
}
|
||||
search = Packages::Nuget::SearchService
|
||||
.new(authorized_user_project, params[:q], search_options)
|
||||
.execute
|
||||
|
||||
track_package_event('search_package', :nuget)
|
||||
|
||||
present ::Packages::Nuget::SearchResultsPresenter.new(search),
|
||||
with: ::API::Entities::Nuget::SearchResults
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,50 +5,113 @@ module Gitlab
|
|||
module Parsers
|
||||
module Coverage
|
||||
class Cobertura
|
||||
CoberturaParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
|
||||
InvalidXMLError = Class.new(Gitlab::Ci::Parsers::ParserError)
|
||||
InvalidLineInformationError = Class.new(Gitlab::Ci::Parsers::ParserError)
|
||||
|
||||
def parse!(xml_data, coverage_report)
|
||||
GO_SOURCE_PATTERN = '/usr/local/go/src'
|
||||
MAX_SOURCES = 100
|
||||
|
||||
def parse!(xml_data, coverage_report, project_path: nil, worktree_paths: nil)
|
||||
root = Hash.from_xml(xml_data)
|
||||
|
||||
parse_all(root, coverage_report)
|
||||
context = {
|
||||
project_path: project_path,
|
||||
paths: worktree_paths&.to_set,
|
||||
sources: []
|
||||
}
|
||||
|
||||
parse_all(root, coverage_report, context)
|
||||
rescue Nokogiri::XML::SyntaxError
|
||||
raise CoberturaParserError, "XML parsing failed"
|
||||
rescue
|
||||
raise CoberturaParserError, "Cobertura parsing failed"
|
||||
raise InvalidXMLError, "XML parsing failed"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_all(root, coverage_report)
|
||||
def parse_all(root, coverage_report, context)
|
||||
return unless root.present?
|
||||
|
||||
root.each do |key, value|
|
||||
parse_node(key, value, coverage_report)
|
||||
parse_node(key, value, coverage_report, context)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_node(key, value, coverage_report)
|
||||
return if key == 'sources'
|
||||
|
||||
if key == 'class'
|
||||
def parse_node(key, value, coverage_report, context)
|
||||
if key == 'sources' && value['source'].present?
|
||||
parse_sources(value['source'], context)
|
||||
elsif key == 'package'
|
||||
Array.wrap(value).each do |item|
|
||||
parse_class(item, coverage_report)
|
||||
parse_package(item, coverage_report, context)
|
||||
end
|
||||
elsif key == 'class'
|
||||
# This means the cobertura XML does not have classes within package nodes.
|
||||
# This is possible in some cases like in simple JS project structures
|
||||
# running Jest.
|
||||
Array.wrap(value).each do |item|
|
||||
parse_class(item, coverage_report, context)
|
||||
end
|
||||
elsif value.is_a?(Hash)
|
||||
parse_all(value, coverage_report)
|
||||
parse_all(value, coverage_report, context)
|
||||
elsif value.is_a?(Array)
|
||||
value.each do |item|
|
||||
parse_all(item, coverage_report)
|
||||
parse_all(item, coverage_report, context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_class(file, coverage_report)
|
||||
def parse_sources(sources, context)
|
||||
return unless context[:project_path] && context[:paths]
|
||||
|
||||
sources = Array.wrap(sources)
|
||||
|
||||
# TODO: Go cobertura has a different format with how their packages
|
||||
# are included in the filename. So we can't rely on the sources.
|
||||
# We'll deal with this later.
|
||||
return if sources.include?(GO_SOURCE_PATTERN)
|
||||
|
||||
sources.each do |source|
|
||||
source = build_source_path(source, context)
|
||||
context[:sources] << source if source.present?
|
||||
end
|
||||
end
|
||||
|
||||
def build_source_path(source, context)
|
||||
# | raw source | extracted |
|
||||
# |-----------------------------|------------|
|
||||
# | /builds/foo/test/SampleLib/ | SampleLib/ |
|
||||
# | /builds/foo/test/something | something |
|
||||
# | /builds/foo/test/ | nil |
|
||||
# | /builds/foo/test | nil |
|
||||
source.split("#{context[:project_path]}/", 2)[1]
|
||||
end
|
||||
|
||||
def parse_package(package, coverage_report, context)
|
||||
classes = package.dig('classes', 'class')
|
||||
return unless classes.present?
|
||||
|
||||
matched_filenames = Array.wrap(classes).map do |item|
|
||||
parse_class(item, coverage_report, context)
|
||||
end
|
||||
|
||||
# Remove these filenames from the paths to avoid conflict
|
||||
# with other packages that may contain the same class filenames
|
||||
remove_matched_filenames(matched_filenames, context)
|
||||
end
|
||||
|
||||
def remove_matched_filenames(filenames, context)
|
||||
return unless context[:paths]
|
||||
|
||||
filenames.each { |f| context[:paths].delete(f) }
|
||||
end
|
||||
|
||||
def parse_class(file, coverage_report, context)
|
||||
return unless file["filename"].present? && file["lines"].present?
|
||||
|
||||
parsed_lines = parse_lines(file["lines"])
|
||||
filename = determine_filename(file["filename"], context)
|
||||
|
||||
coverage_report.add_file(file["filename"], Hash[parsed_lines])
|
||||
coverage_report.add_file(filename, Hash[parsed_lines]) if filename
|
||||
|
||||
filename
|
||||
end
|
||||
|
||||
def parse_lines(lines)
|
||||
|
@ -58,6 +121,27 @@ module Gitlab
|
|||
# Using `Integer()` here to raise exception on invalid values
|
||||
[Integer(line["number"]), Integer(line["hits"])]
|
||||
end
|
||||
rescue
|
||||
raise InvalidLineInformationError, "Line information had invalid values"
|
||||
end
|
||||
|
||||
def determine_filename(filename, context)
|
||||
return filename unless context[:sources].any?
|
||||
|
||||
full_filename = nil
|
||||
|
||||
context[:sources].each_with_index do |source, index|
|
||||
break if index >= MAX_SOURCES
|
||||
break if full_filename = check_source(source, filename, context)
|
||||
end
|
||||
|
||||
full_filename
|
||||
end
|
||||
|
||||
def check_source(source, filename, context)
|
||||
full_path = File.join(source, filename)
|
||||
|
||||
return full_path if context[:paths].include?(full_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,3 +49,32 @@ dast:
|
|||
- if: $CI_COMMIT_BRANCH &&
|
||||
$GITLAB_FEATURES =~ /\bdast\b/ &&
|
||||
$DAST_API_SPECIFICATION
|
||||
|
||||
dast_unlicensed:
|
||||
stage: dast
|
||||
allow_failure: true
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
rules:
|
||||
- if: $DAST_DISABLED
|
||||
when: never
|
||||
- if: $DAST_DISABLED_FOR_DEFAULT_BRANCH &&
|
||||
$CI_DEFAULT_BRANCH == $CI_COMMIT_REF_NAME
|
||||
when: never
|
||||
- if: $CI_DEFAULT_BRANCH != $CI_COMMIT_REF_NAME &&
|
||||
$REVIEW_DISABLED && $DAST_WEBSITE == null &&
|
||||
$DAST_API_SPECIFICATION == null
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH &&
|
||||
$CI_KUBERNETES_ACTIVE &&
|
||||
$GITLAB_FEATURES !~ /\bdast\b/
|
||||
- if: $CI_COMMIT_BRANCH &&
|
||||
$GITLAB_FEATURES !~ /\bdast\b/ &&
|
||||
$DAST_WEBSITE
|
||||
- if: $CI_COMMIT_BRANCH &&
|
||||
$GITLAB_FEATURES !~ /\bdast\b/ &&
|
||||
$DAST_API_SPECIFICATION
|
||||
script:
|
||||
- |
|
||||
echo "Error: Your GitLab project is not licensed for DAST."
|
||||
- exit 1
|
||||
|
|
|
@ -31747,9 +31747,6 @@ msgstr ""
|
|||
msgid "Your U2F device was registered!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your WebAuthn device did not send a valid JSON response."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ Migration/UpdateLargeTable:
|
|||
- :todos
|
||||
- :users
|
||||
- :user_preferences
|
||||
- :user_details
|
||||
- :web_hook_logs
|
||||
DeniedMethods:
|
||||
- :change_column_type_concurrently
|
||||
|
|
|
@ -229,6 +229,16 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :coverage_with_paths_not_relative_to_project_root do
|
||||
file_type { :cobertura }
|
||||
file_format { :gzip }
|
||||
|
||||
after(:build) do |artifact, evaluator|
|
||||
artifact.file = fixture_file_upload(
|
||||
Rails.root.join('spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz'), 'application/x-gzip')
|
||||
end
|
||||
end
|
||||
|
||||
trait :coverage_with_corrupted_data do
|
||||
file_type { :cobertura }
|
||||
file_format { :gzip }
|
||||
|
|
BIN
spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz
vendored
Normal file
BIN
spec/fixtures/cobertura/coverage_with_paths_not_relative_to_project_root.xml.gz
vendored
Normal file
Binary file not shown.
|
@ -14,21 +14,18 @@ describe('Users admin page Modal Manager', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const actionModals = {
|
||||
action1: ModalStub,
|
||||
action2: ModalStub,
|
||||
};
|
||||
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = mount(UserModalManager, {
|
||||
propsData: {
|
||||
actionModals,
|
||||
modalConfiguration,
|
||||
csrfToken: 'dummyCSRF',
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
DeleteUserModal: ModalStub,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -43,11 +40,6 @@ describe('Users admin page Modal Manager', () => {
|
|||
expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('throws if non-existing action is requested', () => {
|
||||
createComponent();
|
||||
expect(() => wrapper.vm.show({ glModalAction: 'non-existing' })).toThrow();
|
||||
});
|
||||
|
||||
it('throws if action has no proper configuration', () => {
|
||||
createComponent({
|
||||
modalConfiguration: {},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import { GlDrawer, GlInfiniteScroll, GlTabs } from '@gitlab/ui';
|
||||
import { GlDrawer, GlInfiniteScroll } from '@gitlab/ui';
|
||||
import { mockTracking, unmockTracking, triggerEvent } from 'helpers/tracking_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import App from '~/whats_new/components/app.vue';
|
||||
|
@ -16,18 +16,12 @@ const localVue = createLocalVue();
|
|||
localVue.use(Vuex);
|
||||
|
||||
describe('App', () => {
|
||||
const propsData = { storageKey: 'storage-key' };
|
||||
let wrapper;
|
||||
let store;
|
||||
let actions;
|
||||
let state;
|
||||
let trackingSpy;
|
||||
let gitlabDotCom = true;
|
||||
|
||||
const buildProps = () => ({
|
||||
storageKey: 'storage-key',
|
||||
versions: ['3.11', '3.10'],
|
||||
gitlabDotCom,
|
||||
});
|
||||
|
||||
const buildWrapper = () => {
|
||||
actions = {
|
||||
|
@ -51,7 +45,7 @@ describe('App', () => {
|
|||
wrapper = mount(App, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: buildProps(),
|
||||
propsData,
|
||||
directives: {
|
||||
GlResizeObserver: createMockDirective(),
|
||||
},
|
||||
|
@ -59,171 +53,112 @@ describe('App', () => {
|
|||
};
|
||||
|
||||
const findInfiniteScroll = () => wrapper.find(GlInfiniteScroll);
|
||||
const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached');
|
||||
|
||||
const setup = async () => {
|
||||
beforeEach(async () => {
|
||||
document.body.dataset.page = 'test-page';
|
||||
document.body.dataset.namespaceId = 'namespace-840';
|
||||
|
||||
trackingSpy = mockTracking('_category_', null, jest.spyOn);
|
||||
buildWrapper();
|
||||
|
||||
wrapper.vm.$store.state.features = [
|
||||
{ title: 'Whats New Drawer', url: 'www.url.com', release: 3.11 },
|
||||
];
|
||||
wrapper.vm.$store.state.features = [{ title: 'Whats New Drawer', url: 'www.url.com' }];
|
||||
wrapper.vm.$store.state.drawerBodyHeight = MOCK_DRAWER_BODY_HEIGHT;
|
||||
await wrapper.vm.$nextTick();
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
unmockTracking();
|
||||
});
|
||||
|
||||
describe('gitlab.com', () => {
|
||||
beforeEach(() => {
|
||||
setup();
|
||||
});
|
||||
const getDrawer = () => wrapper.find(GlDrawer);
|
||||
|
||||
const getDrawer = () => wrapper.find(GlDrawer);
|
||||
it('contains a drawer', () => {
|
||||
expect(getDrawer().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('contains a drawer', () => {
|
||||
expect(getDrawer().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('dispatches openDrawer and tracking calls when mounted', () => {
|
||||
expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', {
|
||||
label: 'namespace_id',
|
||||
value: 'namespace-840',
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches closeDrawer when clicking close', () => {
|
||||
getDrawer().vm.$emit('close');
|
||||
expect(actions.closeDrawer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([true, false])('passes open property', async openState => {
|
||||
wrapper.vm.$store.state.open = openState;
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getDrawer().props('open')).toBe(openState);
|
||||
});
|
||||
|
||||
it('renders features when provided via ajax', () => {
|
||||
expect(actions.fetchItems).toHaveBeenCalled();
|
||||
expect(wrapper.find('[data-test-id="feature-title"]').text()).toBe('Whats New Drawer');
|
||||
});
|
||||
|
||||
it('send an event when feature item is clicked', () => {
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
|
||||
const link = wrapper.find('.whats-new-item-title-link');
|
||||
triggerEvent(link.element);
|
||||
|
||||
expect(trackingSpy.mock.calls[1]).toMatchObject([
|
||||
'_category_',
|
||||
'click_whats_new_item',
|
||||
{
|
||||
label: 'Whats New Drawer',
|
||||
property: 'www.url.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders infinite scroll', () => {
|
||||
const scroll = findInfiniteScroll();
|
||||
|
||||
expect(scroll.props()).toMatchObject({
|
||||
fetchedItems: wrapper.vm.$store.state.features.length,
|
||||
maxListHeight: MOCK_DRAWER_BODY_HEIGHT,
|
||||
});
|
||||
});
|
||||
|
||||
describe('bottomReached', () => {
|
||||
const emitBottomReached = () => findInfiniteScroll().vm.$emit('bottomReached');
|
||||
|
||||
beforeEach(() => {
|
||||
actions.fetchItems.mockClear();
|
||||
});
|
||||
|
||||
it('when nextPage exists it calls fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: 840 };
|
||||
emitBottomReached();
|
||||
|
||||
expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { page: 840 });
|
||||
});
|
||||
|
||||
it('when nextPage does not exist it does not call fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: null };
|
||||
emitBottomReached();
|
||||
|
||||
expect(actions.fetchItems).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => {
|
||||
const { value } = getBinding(getDrawer().element, 'gl-resize-observer');
|
||||
|
||||
value();
|
||||
|
||||
expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element);
|
||||
|
||||
expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
MOCK_DRAWER_BODY_HEIGHT,
|
||||
);
|
||||
it('dispatches openDrawer and tracking calls when mounted', () => {
|
||||
expect(actions.openDrawer).toHaveBeenCalledWith(expect.any(Object), 'storage-key');
|
||||
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_whats_new_drawer', {
|
||||
label: 'namespace_id',
|
||||
value: 'namespace-840',
|
||||
});
|
||||
});
|
||||
|
||||
describe('self managed', () => {
|
||||
const findTabs = () => wrapper.find(GlTabs);
|
||||
it('dispatches closeDrawer when clicking close', () => {
|
||||
getDrawer().vm.$emit('close');
|
||||
expect(actions.closeDrawer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const clickSecondTab = async () => {
|
||||
const secondTab = wrapper.findAll('.nav-link').at(1);
|
||||
await secondTab.trigger('click');
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
};
|
||||
it.each([true, false])('passes open property', async openState => {
|
||||
wrapper.vm.$store.state.open = openState;
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getDrawer().props('open')).toBe(openState);
|
||||
});
|
||||
|
||||
it('renders features when provided via ajax', () => {
|
||||
expect(actions.fetchItems).toHaveBeenCalled();
|
||||
expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
|
||||
});
|
||||
|
||||
it('send an event when feature item is clicked', () => {
|
||||
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
|
||||
|
||||
const link = wrapper.find('.whats-new-item-title-link');
|
||||
triggerEvent(link.element);
|
||||
|
||||
expect(trackingSpy.mock.calls[1]).toMatchObject([
|
||||
'_category_',
|
||||
'click_whats_new_item',
|
||||
{
|
||||
label: 'Whats New Drawer',
|
||||
property: 'www.url.com',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders infinite scroll', () => {
|
||||
const scroll = findInfiniteScroll();
|
||||
|
||||
expect(scroll.props()).toMatchObject({
|
||||
fetchedItems: wrapper.vm.$store.state.features.length,
|
||||
maxListHeight: MOCK_DRAWER_BODY_HEIGHT,
|
||||
});
|
||||
});
|
||||
|
||||
describe('bottomReached', () => {
|
||||
beforeEach(() => {
|
||||
gitlabDotCom = false;
|
||||
setup();
|
||||
actions.fetchItems.mockClear();
|
||||
});
|
||||
|
||||
it('renders tabs with drawer body height and content', () => {
|
||||
const scroll = findInfiniteScroll();
|
||||
const tabs = findTabs();
|
||||
it('when nextPage exists it calls fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: 840 };
|
||||
emitBottomReached();
|
||||
|
||||
expect(scroll.exists()).toBe(false);
|
||||
expect(tabs.attributes().style).toBe(`height: ${MOCK_DRAWER_BODY_HEIGHT}px;`);
|
||||
expect(wrapper.find('h5').text()).toBe('Whats New Drawer');
|
||||
expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), 840);
|
||||
});
|
||||
|
||||
describe('fetchVersion', () => {
|
||||
beforeEach(() => {
|
||||
actions.fetchItems.mockClear();
|
||||
});
|
||||
it('when nextPage does not exist it does not call fetchItems', () => {
|
||||
wrapper.vm.$store.state.pageInfo = { nextPage: null };
|
||||
emitBottomReached();
|
||||
|
||||
it('when version isnt fetched, clicking a tab calls fetchItems', async () => {
|
||||
const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion');
|
||||
await clickSecondTab();
|
||||
|
||||
expect(fetchVersionSpy).toHaveBeenCalledWith('3.10');
|
||||
expect(actions.fetchItems).toHaveBeenCalledWith(expect.anything(), { version: '3.10' });
|
||||
});
|
||||
|
||||
it('when version has been fetched, clicking a tab calls fetchItems', async () => {
|
||||
wrapper.vm.$store.state.features.push({ title: 'GitLab Stories', release: 3.1 });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const fetchVersionSpy = jest.spyOn(wrapper.vm, 'fetchVersion');
|
||||
await clickSecondTab();
|
||||
|
||||
expect(fetchVersionSpy).toHaveBeenCalledWith('3.10');
|
||||
expect(actions.fetchItems).not.toHaveBeenCalled();
|
||||
expect(wrapper.find('.tab-pane.active h5').text()).toBe('GitLab Stories');
|
||||
});
|
||||
expect(actions.fetchItems).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls getDrawerBodyHeight and setDrawerBodyHeight when resize directive is triggered', () => {
|
||||
const { value } = getBinding(getDrawer().element, 'gl-resize-observer');
|
||||
|
||||
value();
|
||||
|
||||
expect(getDrawerBodyHeight).toHaveBeenCalledWith(wrapper.find(GlDrawer).element);
|
||||
|
||||
expect(actions.setDrawerBodyHeight).toHaveBeenCalledWith(
|
||||
expect.any(Object),
|
||||
MOCK_DRAWER_BODY_HEIGHT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,23 +41,6 @@ describe('whats new actions', () => {
|
|||
axiosMock.restore();
|
||||
});
|
||||
|
||||
it('passes arguments', () => {
|
||||
axiosMock.reset();
|
||||
|
||||
axiosMock
|
||||
.onGet('/-/whats_new', { params: { page: 8, version: 40 } })
|
||||
.replyOnce(200, [{ title: 'GitLab Stories' }]);
|
||||
|
||||
testAction(
|
||||
actions.fetchItems,
|
||||
{ page: 8, version: 40 },
|
||||
{},
|
||||
expect.arrayContaining([
|
||||
{ type: types.ADD_FEATURES, payload: [{ title: 'GitLab Stories' }] },
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('if already fetching, does not fetch', () => {
|
||||
testAction(actions.fetchItems, {}, { fetching: true }, []);
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe WhatsNewHelper do
|
|||
let(:release_item) { double(:item) }
|
||||
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:versions).and_return([84.0])
|
||||
allow(ReleaseHighlight).to receive(:most_recent_version).and_return(84.0)
|
||||
end
|
||||
|
||||
it { is_expected.to eq('display-whats-new-notification-84.0') }
|
||||
|
@ -18,7 +18,7 @@ RSpec.describe WhatsNewHelper do
|
|||
|
||||
context 'when most recent release highlights do NOT exist' do
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:versions).and_return(nil)
|
||||
allow(ReleaseHighlight).to receive(:most_recent_version).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
|
@ -44,14 +44,4 @@ RSpec.describe WhatsNewHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#whats_new_versions' do
|
||||
let(:versions) { [84.0] }
|
||||
|
||||
it 'returns ReleaseHighlight.versions' do
|
||||
expect(ReleaseHighlight).to receive(:versions).and_return(versions)
|
||||
|
||||
expect(helper.whats_new_versions).to eq(versions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,199 +4,682 @@ require 'fast_spec_helper'
|
|||
|
||||
RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
|
||||
describe '#parse!' do
|
||||
subject { described_class.new.parse!(cobertura, coverage_report) }
|
||||
subject(:parse_report) { described_class.new.parse!(cobertura, coverage_report, project_path: project_path, worktree_paths: paths) }
|
||||
|
||||
let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new }
|
||||
let(:project_path) { 'foo/bar' }
|
||||
let(:paths) { ['app/user.rb'] }
|
||||
|
||||
let(:cobertura) do
|
||||
<<~EOF
|
||||
<coverage>
|
||||
#{sources_xml}
|
||||
#{classes_xml}
|
||||
</coverage>
|
||||
EOF
|
||||
end
|
||||
|
||||
context 'when data is Cobertura style XML' do
|
||||
context 'when there is no <class>' do
|
||||
let(:cobertura) { '' }
|
||||
shared_examples_for 'ignoring sources, project_path, and worktree_paths' do
|
||||
context 'when there is no <class>' do
|
||||
let(:classes_xml) { '' }
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { subject }.not_to raise_error
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a single <class>' do
|
||||
context 'with no lines' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single line' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a package parent' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages>
|
||||
<class filename="app.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple lines and methods info' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are multiple <class>' do
|
||||
context 'without a package parent' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
<class filename="foo.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
</lines></class>
|
||||
</packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns coverage information per class' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 }, 'foo.rb' => { 6 => 1 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the same filename and different lines' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with merged coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the same filename and lines' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="1"/>
|
||||
<line number="2" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with summed-up coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing filename' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and ignores class with missing name' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid line information' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line null="test" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { parse_report }.to raise_error(described_class::InvalidLineInformationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no <sources>' do
|
||||
let(:sources_xml) { '' }
|
||||
|
||||
it_behaves_like 'ignoring sources, project_path, and worktree_paths'
|
||||
end
|
||||
|
||||
context 'when there is a <sources>' do
|
||||
shared_examples_for 'ignoring sources' do
|
||||
it 'parses XML without errors' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'and has a single source' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
context 'and has a single source with a pattern for Go projects' do
|
||||
let(:project_path) { 'local/go' } # Make sure we're not making false positives
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>project/src</source>
|
||||
<source>/usr/local/go/src</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
it_behaves_like 'ignoring sources'
|
||||
it_behaves_like 'ignoring sources, project_path, and worktree_paths'
|
||||
end
|
||||
|
||||
context 'and has multiple sources' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
context 'and has multiple sources with a pattern for Go projects' do
|
||||
let(:project_path) { 'local/go' } # Make sure we're not making false positives
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>project/src/foo</source>
|
||||
<source>project/src/bar</source>
|
||||
<source>/usr/local/go/src</source>
|
||||
<source>/go/src</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
it_behaves_like 'ignoring sources'
|
||||
it_behaves_like 'ignoring sources, project_path, and worktree_paths'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a single <class>' do
|
||||
context 'with no lines' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<classes><class filename="app.rb"></class></classes>
|
||||
context 'and has a single source but already is at the project root path' do
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>builds/#{project_path}</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
it_behaves_like 'ignoring sources, project_path, and worktree_paths'
|
||||
end
|
||||
|
||||
context 'with a single line' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<classes>
|
||||
<class filename="app.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
context 'and has multiple sources but already are at the project root path' do
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>builds/#{project_path}/</source>
|
||||
<source>builds/somewhere/#{project_path}</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with coverage' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2 } })
|
||||
end
|
||||
it_behaves_like 'ignoring sources, project_path, and worktree_paths'
|
||||
end
|
||||
|
||||
context 'with multipe lines and methods info' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
context 'and has a single source that is not at the project root path' do
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>builds/#{project_path}/app</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with coverage' do
|
||||
expect { subject }.not_to raise_error
|
||||
context 'when there is no <class>' do
|
||||
let(:classes_xml) { '' }
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a single <class>' do
|
||||
context 'with no lines' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single line but the filename cannot be determined based on extracted source and worktree paths' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="member.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single line' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with the filename relative to project root' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple lines and methods info' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with the filename relative to project root' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are multiple <class>' do
|
||||
context 'with the same filename but the filename cannot be determined based on extracted source and worktree paths' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="member.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="member.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a parent package' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns coverage information with the filename relative to project root' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the same filename and different lines' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with merged coverage, and with the filename relative to project root' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the same filename and lines' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="1"/>
|
||||
<line number="2" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with summed-up coverage, and with the filename relative to project root' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 3, 2 => 1 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing filename' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and ignores class with missing name' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with filename that cannot be determined based on extracted source and worktree paths' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="member.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and ignores class with undetermined filename' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app/user.rb' => { 1 => 2, 2 => 0 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid line information' do
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="user.rb"><methods/><lines>
|
||||
<line null="test" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { parse_report }.to raise_error(described_class::InvalidLineInformationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'and has multiple sources that are not at the project root path' do
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>builds/#{project_path}/app1/</source>
|
||||
<source>builds/#{project_path}/app2/</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
context 'and a class filename is available under multiple extracted sources' do
|
||||
let(:paths) { ['app1/user.rb', 'app2/user.rb'] }
|
||||
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<package name="app1">
|
||||
<classes>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
</package>
|
||||
<package name="app2">
|
||||
<classes>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="2" hits="3"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
</package>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns the files with the filename relative to project root' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({
|
||||
'app1/user.rb' => { 1 => 2 },
|
||||
'app2/user.rb' => { 2 => 3 }
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'and a class filename is available under one of the extracted sources' do
|
||||
let(:paths) { ['app1/member.rb', 'app2/user.rb', 'app2/pet.rb'] }
|
||||
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with the filename relative to project root using the extracted source where it is first found under' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app2/user.rb' => { 1 => 2 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'and a class filename is not found under any of the extracted sources' do
|
||||
let(:paths) { ['app1/member.rb', 'app2/pet.rb'] }
|
||||
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'and a class filename is not found under any of the extracted sources within the iteratable limit' do
|
||||
let(:paths) { ['app2/user.rb'] }
|
||||
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="record.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const("#{described_class}::MAX_SOURCES", 1)
|
||||
end
|
||||
|
||||
it 'parses XML and returns empty coverage' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are multipe <class>' do
|
||||
context 'with the same filename and different lines' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with merged coverage' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0, 6 => 1, 7 => 1 } })
|
||||
end
|
||||
shared_examples_for 'non-smart parsing' do
|
||||
let(:sources_xml) do
|
||||
<<~EOF
|
||||
<sources>
|
||||
<source>builds/foo/bar/app</source>
|
||||
</sources>
|
||||
EOF
|
||||
end
|
||||
|
||||
context 'with the same filename and lines' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<packages><package><classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="1"/>
|
||||
<line number="2" hits="1"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'parses XML and returns a single file with summed-up coverage' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 3, 2 => 1 } })
|
||||
end
|
||||
let(:classes_xml) do
|
||||
<<~EOF
|
||||
<packages><package name="app"><classes>
|
||||
<class filename="user.rb"><lines>
|
||||
<line number="1" hits="2"/>
|
||||
</lines></class>
|
||||
</classes></package></packages>
|
||||
EOF
|
||||
end
|
||||
|
||||
context 'with missing filename' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class><methods/><lines>
|
||||
<line number="6" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
EOF
|
||||
end
|
||||
it 'parses XML and returns filenames unchanged just as how they are found in the class node' do
|
||||
expect { parse_report }.not_to raise_error
|
||||
|
||||
it 'parses XML and ignores class with missing name' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files).to eq({ 'app.rb' => { 1 => 2, 2 => 0 } })
|
||||
end
|
||||
expect(coverage_report.files).to eq({ 'user.rb' => { 1 => 2 } })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid line information' do
|
||||
let(:cobertura) do
|
||||
<<-EOF.strip_heredoc
|
||||
<classes>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line number="1" hits="2"/>
|
||||
<line number="2" hits="0"/>
|
||||
</lines></class>
|
||||
<class filename="app.rb"><methods/><lines>
|
||||
<line null="test" hits="1"/>
|
||||
<line number="7" hits="1"/>
|
||||
</lines></class>
|
||||
</classes>
|
||||
EOF
|
||||
end
|
||||
context 'when project_path is not present' do
|
||||
let(:project_path) { nil }
|
||||
let(:paths) { ['app/user.rb'] }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(described_class::CoberturaParserError)
|
||||
end
|
||||
end
|
||||
it_behaves_like 'non-smart parsing'
|
||||
end
|
||||
|
||||
context 'when worktree_paths is not present' do
|
||||
let(:project_path) { 'foo/bar' }
|
||||
let(:paths) { nil }
|
||||
|
||||
it_behaves_like 'non-smart parsing'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -204,7 +687,7 @@ RSpec.describe Gitlab::Ci::Parsers::Coverage::Cobertura do
|
|||
let(:cobertura) { { coverage: '12%' }.to_json }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(described_class::CoberturaParserError)
|
||||
expect { parse_report }.to raise_error(described_class::InvalidXMLError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4059,13 +4059,40 @@ RSpec.describe Ci::Build do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there is a Cobertura coverage report with class filename paths not relative to project root' do
|
||||
before do
|
||||
allow(build.project).to receive(:full_path).and_return('root/javademo')
|
||||
allow(build.pipeline).to receive(:all_worktree_paths).and_return(['src/main/java/com/example/javademo/User.java'])
|
||||
|
||||
create(:ci_job_artifact, :coverage_with_paths_not_relative_to_project_root, job: build, project: build.project)
|
||||
end
|
||||
|
||||
it 'parses blobs and add the results to the coverage report with corrected paths' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files.keys).to match_array(['src/main/java/com/example/javademo/User.java'])
|
||||
end
|
||||
|
||||
context 'and smart_cobertura_parser feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(smart_cobertura_parser: false)
|
||||
end
|
||||
|
||||
it 'parses blobs and add the results to the coverage report with unmodified paths' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(coverage_report.files.keys).to match_array(['com/example/javademo/User.java'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a corrupted Cobertura coverage report' do
|
||||
before do
|
||||
create(:ci_job_artifact, :coverage_with_corrupted_data, job: build, project: build.project)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::CoberturaParserError)
|
||||
expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::InvalidLineInformationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2578,6 +2578,14 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
it 'receives a pending event once' do
|
||||
expect(WebMock).to have_requested_pipeline_hook('pending').once
|
||||
end
|
||||
|
||||
it 'builds hook data once' do
|
||||
create(:pipelines_email_service, project: project)
|
||||
|
||||
expect(Gitlab::DataBuilder::Pipeline).to receive(:build).once.and_call_original
|
||||
|
||||
pipeline.execute_hooks
|
||||
end
|
||||
end
|
||||
|
||||
context 'when build is run' do
|
||||
|
@ -2639,6 +2647,12 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
it 'did not execute pipeline_hook after touched' do
|
||||
expect(WebMock).not_to have_requested(:post, hook.url)
|
||||
end
|
||||
|
||||
it 'does not build hook data' do
|
||||
expect(Gitlab::DataBuilder::Pipeline).not_to receive(:build)
|
||||
|
||||
pipeline.execute_hooks
|
||||
end
|
||||
end
|
||||
|
||||
def create_build(name, stage_idx)
|
||||
|
@ -3404,6 +3418,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
|
|||
])
|
||||
end
|
||||
|
||||
it 'does not execute N+1 queries' do
|
||||
single_build_pipeline = create(:ci_empty_pipeline, status: :created, project: project)
|
||||
single_rspec = create(:ci_build, :success, name: 'rspec', pipeline: single_build_pipeline, project: project)
|
||||
create(:ci_job_artifact, :cobertura, job: single_rspec, project: project)
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new { single_build_pipeline.coverage_reports }
|
||||
|
||||
expect { subject }.not_to exceed_query_limit(control)
|
||||
end
|
||||
|
||||
context 'when builds are retried' do
|
||||
let!(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) }
|
||||
let!(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) }
|
||||
|
|
|
@ -3,44 +3,21 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ReleaseHighlight do
|
||||
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
|
||||
let(:cache_mock) { double(:cache_mock) }
|
||||
|
||||
before do
|
||||
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
|
||||
allow(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield
|
||||
end
|
||||
|
||||
after do
|
||||
ReleaseHighlight.instance_variable_set(:@file_paths, nil)
|
||||
end
|
||||
|
||||
describe '.for_version' do
|
||||
subject { ReleaseHighlight.for_version(version: version) }
|
||||
|
||||
let(:version) { '1.1' }
|
||||
|
||||
context 'with version param that exists' do
|
||||
it 'returns items from that version' do
|
||||
expect(subject.items.first['title']).to eq("It's gonna be a bright")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with version param that does NOT exist' do
|
||||
let(:version) { '84.0' }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.paginated' do
|
||||
describe '#paginated' do
|
||||
let(:fixture_dir_glob) { Dir.glob(File.join('spec', 'fixtures', 'whats_new', '*.yml')) }
|
||||
let(:cache_mock) { double(:cache_mock) }
|
||||
let(:dot_com) { false }
|
||||
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(dot_com)
|
||||
allow(Dir).to receive(:glob).with(Rails.root.join('data', 'whats_new', '*.yml')).and_return(fixture_dir_glob)
|
||||
|
||||
expect(Rails).to receive(:cache).twice.and_return(cache_mock)
|
||||
expect(cache_mock).to receive(:fetch).with('release_highlight:file_paths', expires_in: 1.hour).and_yield
|
||||
end
|
||||
|
||||
after do
|
||||
ReleaseHighlight.instance_variable_set(:@file_paths, nil)
|
||||
end
|
||||
|
||||
context 'with page param' do
|
||||
|
@ -113,12 +90,35 @@ RSpec.describe ReleaseHighlight do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.most_recent_item_count' do
|
||||
describe '.most_recent_version' do
|
||||
subject { ReleaseHighlight.most_recent_version }
|
||||
|
||||
context 'when version exist' do
|
||||
let(:release_item) { double(:item) }
|
||||
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [release_item] })
|
||||
allow(release_item).to receive(:[]).with('release').and_return(84.0)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(84.0) }
|
||||
end
|
||||
|
||||
context 'when most recent release highlights do NOT exist' do
|
||||
before do
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#most_recent_item_count' do
|
||||
subject { ReleaseHighlight.most_recent_item_count }
|
||||
|
||||
context 'when recent release items exist' do
|
||||
it 'returns the count from the most recent file' do
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return(double(:paginated, items: [double(:item)]))
|
||||
allow(ReleaseHighlight).to receive(:paginated).and_return({ items: [double(:item)] })
|
||||
|
||||
expect(subject).to eq(1)
|
||||
end
|
||||
|
@ -132,32 +132,4 @@ RSpec.describe ReleaseHighlight do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.versions' do
|
||||
it 'returns versions from the file paths' do
|
||||
expect(ReleaseHighlight.versions).to eq(['1.5', '1.2', '1.1'])
|
||||
end
|
||||
|
||||
context 'when there are more than 12 versions' do
|
||||
let(:file_paths) do
|
||||
i = 0
|
||||
Array.new(20) { "20201225_01_#{i += 1}.yml" }
|
||||
end
|
||||
|
||||
it 'limits to 12 versions' do
|
||||
allow(ReleaseHighlight).to receive(:file_paths).and_return(file_paths)
|
||||
expect(ReleaseHighlight.versions.count).to eq(12)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'QueryResult' do
|
||||
subject { ReleaseHighlight::QueryResult.new(items: items, next_page: 2) }
|
||||
|
||||
let(:items) { [:item] }
|
||||
|
||||
it 'responds to map' do
|
||||
expect(subject.map(&:to_s)).to eq(items.map(&:to_s))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,533 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::NugetPackages do
|
||||
include WorkhorseHelpers
|
||||
include PackagesManagerApiSpecHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :public) }
|
||||
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) }
|
||||
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget' do
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'personal token' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with job token' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') }
|
||||
let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
|
||||
let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
|
||||
let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
|
||||
let(:headers) { {} }
|
||||
|
||||
subject { put api(url), headers: headers }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
|
||||
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package uploads'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v4/projects/:id/packages/nuget' do
|
||||
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
|
||||
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
|
||||
let_it_be(:file_name) { 'package.nupkg' }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget" }
|
||||
let(:headers) { {} }
|
||||
let(:params) { { package: temp_file(file_name) } }
|
||||
let(:file_key) { :package }
|
||||
let(:send_rewritten_field) { true }
|
||||
|
||||
subject do
|
||||
workhorse_finalize(
|
||||
api(url),
|
||||
method: :put,
|
||||
file_key: file_key,
|
||||
params: params,
|
||||
headers: headers,
|
||||
send_rewritten_field: send_rewritten_field
|
||||
)
|
||||
end
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
|
||||
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package uploads'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
|
||||
context 'file size above maximum limit' do
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(UploadedFile) do |uploaded_file|
|
||||
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
|
||||
include_context 'with expected presenters dependency groups'
|
||||
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
|
||||
let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
before do
|
||||
packages.each { |pkg| create_dependencies_for(pkg) }
|
||||
end
|
||||
|
||||
context 'without the need for license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
|
||||
include_context 'with expected presenters dependency groups'
|
||||
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
|
||||
let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
before do
|
||||
create_dependencies_for(package)
|
||||
end
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
context 'with invalid package name' do
|
||||
let_it_be(:package_name) { 'Unkown' }
|
||||
|
||||
it_behaves_like 'rejects nuget packages access', :developer, :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
|
||||
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/query' do
|
||||
let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
|
||||
let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
|
||||
let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
|
||||
let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
|
||||
let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
|
||||
let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
|
||||
let(:search_term) { 'uMmy' }
|
||||
let(:take) { 26 }
|
||||
let(:skip) { 0 }
|
||||
let(:include_prereleases) { true }
|
||||
let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,280 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::NugetProjectPackages do
|
||||
include WorkhorseHelpers
|
||||
include PackagesManagerApiSpecHelpers
|
||||
include HttpBasicAuthHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :public) }
|
||||
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) }
|
||||
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget' do
|
||||
it_behaves_like 'handling nuget service requests' do
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/index.json" }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/index' do
|
||||
it_behaves_like 'handling nuget metadata requests with package name' do
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/index.json" }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/metadata/*package_name/*package_version' do
|
||||
it_behaves_like 'handling nuget metadata requests with package name and package version' do
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/metadata/#{package_name}/#{package.version}.json" }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/query' do
|
||||
it_behaves_like 'handling nuget search requests' do
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/query?#{query_parameters.to_query}" }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
|
||||
let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
|
||||
let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/authorize" }
|
||||
let(:headers) { {} }
|
||||
|
||||
subject { put api(url), headers: headers }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget workhorse authorization' | :success
|
||||
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package uploads'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v4/projects/:id/packages/nuget' do
|
||||
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
|
||||
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
|
||||
let_it_be(:file_name) { 'package.nupkg' }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget" }
|
||||
let(:headers) { {} }
|
||||
let(:params) { { package: temp_file(file_name) } }
|
||||
let(:file_key) { :package }
|
||||
let(:send_rewritten_field) { true }
|
||||
|
||||
subject do
|
||||
workhorse_finalize(
|
||||
api(url),
|
||||
method: :put,
|
||||
file_key: file_key,
|
||||
params: params,
|
||||
headers: headers,
|
||||
send_rewritten_field: send_rewritten_field
|
||||
)
|
||||
end
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget upload' | :created
|
||||
'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process nuget upload' | :created
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
let(:headers) { user_headers.merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package uploads'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
|
||||
context 'file size above maximum limit' do
|
||||
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(UploadedFile) do |uploaded_file|
|
||||
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/index' do
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:packages) { create_list(:nuget_package, 5, name: package_name, project: project) }
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package_name}/index.json" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget download versions request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget download versions request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget download versions request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:package) { create(:nuget_package, project: project, name: package_name) }
|
||||
|
||||
let(:url) { "/projects/#{project.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.nupkg" }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget download content request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget download content request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget download content request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,22 +4,22 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe WhatsNewController do
|
||||
describe 'whats_new_path' do
|
||||
let(:item) { double(:item) }
|
||||
let(:highlights) { double(:highlight, items: [item], map: [item].map, next_page: 2) }
|
||||
|
||||
context 'with whats_new_drawer feature enabled' do
|
||||
before do
|
||||
stub_feature_flags(whats_new_drawer: true)
|
||||
end
|
||||
|
||||
context 'with no page param' do
|
||||
let(:most_recent) { { items: [item], next_page: 2 } }
|
||||
let(:item) { double(:item) }
|
||||
|
||||
it 'responds with paginated data and headers' do
|
||||
allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(highlights)
|
||||
allow(ReleaseHighlight).to receive(:paginated).with(page: 1).and_return(most_recent)
|
||||
allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
|
||||
|
||||
get whats_new_path, xhr: true
|
||||
|
||||
expect(response.body).to eq(highlights.items.to_json)
|
||||
expect(response.body).to eq(most_recent[:items].to_json)
|
||||
expect(response.headers['X-Next-Page']).to eq(2)
|
||||
end
|
||||
end
|
||||
|
@ -37,18 +37,6 @@ RSpec.describe WhatsNewController do
|
|||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with version param' do
|
||||
it 'returns items without pagination headers' do
|
||||
allow(ReleaseHighlight).to receive(:for_version).with(version: '42').and_return(highlights)
|
||||
allow(Gitlab::WhatsNew::ItemPresenter).to receive(:present).with(item).and_return(item)
|
||||
|
||||
get whats_new_path(version: 42), xhr: true
|
||||
|
||||
expect(response.body).to eq(highlights.items.to_json)
|
||||
expect(response.headers['X-Next-Page']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with whats_new_drawer feature disabled' do
|
||||
|
|
|
@ -7,7 +7,8 @@ RSpec.describe ::Ci::Pipelines::CreateArtifactService do
|
|||
subject { described_class.new.execute(pipeline) }
|
||||
|
||||
context 'when pipeline has coverage reports' do
|
||||
let(:pipeline) { create(:ci_pipeline, :with_coverage_reports) }
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:pipeline) { create(:ci_pipeline, :with_coverage_reports, project: project) }
|
||||
|
||||
context 'when pipeline is finished' do
|
||||
it 'creates a pipeline artifact' do
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'handling nuget service requests' do
|
||||
subject { get api(url) }
|
||||
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
context 'personal token' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
context 'with job token' do
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') }
|
||||
let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'handling nuget metadata requests with package name' do
|
||||
include_context 'with expected presenters dependency groups'
|
||||
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:packages) { create_list(:nuget_package, 5, :with_metadatum, name: package_name, project: project) }
|
||||
let_it_be(:tags) { packages.each { |pkg| create(:packages_tag, package: pkg, name: 'test') } }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
before do
|
||||
packages.each { |pkg| create_dependencies_for(pkg) }
|
||||
end
|
||||
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name level' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name level' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name level' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'handling nuget metadata requests with package name and package version' do
|
||||
include_context 'with expected presenters dependency groups'
|
||||
|
||||
let_it_be(:package_name) { 'Dummy.Package' }
|
||||
let_it_be(:package) { create(:nuget_package, :with_metadatum, name: 'Dummy.Package', project: project) }
|
||||
let_it_be(:tag) { create(:packages_tag, package: package, name: 'test') }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
before do
|
||||
create_dependencies_for(package)
|
||||
end
|
||||
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget metadata request at package name and package version level' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
context 'with invalid package name' do
|
||||
let_it_be(:package_name) { 'Unkown' }
|
||||
|
||||
it_behaves_like 'rejects nuget packages access', :developer, :not_found
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'handling nuget search requests' do
|
||||
let_it_be(:package_a) { create(:nuget_package, :with_metadatum, name: 'Dummy.PackageA', project: project) }
|
||||
let_it_be(:tag) { create(:packages_tag, package: package_a, name: 'test') }
|
||||
let_it_be(:packages_b) { create_list(:nuget_package, 5, name: 'Dummy.PackageB', project: project) }
|
||||
let_it_be(:packages_c) { create_list(:nuget_package, 5, name: 'Dummy.PackageC', project: project) }
|
||||
let_it_be(:package_d) { create(:nuget_package, name: 'Dummy.PackageD', version: '5.0.5-alpha', project: project) }
|
||||
let_it_be(:package_e) { create(:nuget_package, name: 'Foo.BarE', project: project) }
|
||||
let(:search_term) { 'uMmy' }
|
||||
let(:take) { 26 }
|
||||
let(:skip) { 0 }
|
||||
let(:include_prereleases) { true }
|
||||
let(:query_parameters) { { q: search_term, take: take, skip: skip, prerelease: include_prereleases } }
|
||||
|
||||
subject { get api(url) }
|
||||
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'process nuget search request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'process nuget search request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'process nuget search request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'process nuget search request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'deploy token for package GET requests'
|
||||
|
||||
it_behaves_like 'rejects nuget access with unknown project id'
|
||||
|
||||
it_behaves_like 'rejects nuget access with invalid project id'
|
||||
end
|
|
@ -26,7 +26,7 @@ RSpec.shared_examples 'process nuget service index request' do |user_type, statu
|
|||
|
||||
it_behaves_like 'returning response status', status
|
||||
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'cli_metadata'
|
||||
it_behaves_like 'a package tracking event', 'API::NugetPackages', 'cli_metadata'
|
||||
|
||||
it 'returns a valid json response' do
|
||||
subject
|
||||
|
@ -169,7 +169,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
|
|||
context 'with correct params' do
|
||||
it_behaves_like 'package workhorse uploads'
|
||||
it_behaves_like 'creates nuget package files'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'push_package'
|
||||
it_behaves_like 'a package tracking event', 'API::NugetPackages', 'push_package'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -286,7 +286,7 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
|
|||
|
||||
it_behaves_like 'returning response status', status
|
||||
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
|
||||
it_behaves_like 'a package tracking event', 'API::NugetPackages', 'pull_package'
|
||||
|
||||
it 'returns a valid package archive' do
|
||||
subject
|
||||
|
@ -336,7 +336,7 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_
|
|||
|
||||
it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
|
||||
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'search_package'
|
||||
it_behaves_like 'a package tracking event', 'API::NugetPackages', 'search_package'
|
||||
|
||||
context 'with skip set to 2' do
|
||||
let(:skip) { 2 }
|
||||
|
|
Loading…
Reference in New Issue