Commit 8f778ed5 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 3b8b1417 8753c663
...@@ -141,6 +141,9 @@ export default { ...@@ -141,6 +141,9 @@ export default {
isInstalling() { isInstalling() {
return this.status === APPLICATION_STATUS.INSTALLING; return this.status === APPLICATION_STATUS.INSTALLING;
}, },
isExternallyInstalled() {
return this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED;
},
canInstall() { canInstall() {
return ( return (
this.status === APPLICATION_STATUS.NOT_INSTALLABLE || this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
...@@ -193,10 +196,17 @@ export default { ...@@ -193,10 +196,17 @@ export default {
label = __('Installing'); label = __('Installing');
} else if (this.installed) { } else if (this.installed) {
label = __('Installed'); label = __('Installed');
} else if (this.isExternallyInstalled) {
label = __('Externally installed');
} }
return label; return label;
}, },
buttonGridCellClass() {
return this.showManageButton || this.status === APPLICATION_STATUS.EXTERNALLY_INSTALLED
? 'section-25'
: 'section-15';
},
showManageButton() { showManageButton() {
return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED; return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
}, },
...@@ -427,8 +437,7 @@ export default { ...@@ -427,8 +437,7 @@ export default {
</div> </div>
</div> </div>
<div <div
:class="{ 'section-25': showManageButton, 'section-15': !showManageButton }" :class="[buttonGridCellClass, 'table-section', 'table-button-footer', 'section-align-top']"
class="table-section table-button-footer section-align-top"
role="gridcell" role="gridcell"
> >
<div v-if="showManageButton" class="btn-group table-action-buttons"> <div v-if="showManageButton" class="btn-group table-action-buttons">
......
...@@ -26,6 +26,7 @@ export const APPLICATION_STATUS = { ...@@ -26,6 +26,7 @@ export const APPLICATION_STATUS = {
ERROR: 'errored', ERROR: 'errored',
PRE_INSTALLED: 'pre_installed', PRE_INSTALLED: 'pre_installed',
UNINSTALLED: 'uninstalled', UNINSTALLED: 'uninstalled',
EXTERNALLY_INSTALLED: 'externally_installed',
}; };
/* /*
......
import Vue from 'vue'; import Vue from 'vue';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import IntegrationForm from '../components/integration_form.vue'; import IntegrationForm from '../components/integration_form.vue';
import { createStore } from '../stores'; import { createStore } from '../stores';
export default () => { export default () => {
dirtySubmitFactory(document.querySelectorAll('.js-cluster-integrations-form'));
const entryPoint = document.querySelector('#js-cluster-details-form'); const entryPoint = document.querySelector('#js-cluster-details-form');
if (!entryPoint) { if (!entryPoint) {
......
...@@ -15,6 +15,7 @@ const { ...@@ -15,6 +15,7 @@ const {
UNINSTALL_ERRORED, UNINSTALL_ERRORED,
PRE_INSTALLED, PRE_INSTALLED,
UNINSTALLED, UNINSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS; } = APPLICATION_STATUS;
const applicationStateMachine = { const applicationStateMachine = {
...@@ -71,6 +72,9 @@ const applicationStateMachine = { ...@@ -71,6 +72,9 @@ const applicationStateMachine = {
[UNINSTALLED]: { [UNINSTALLED]: {
target: UNINSTALLED, target: UNINSTALLED,
}, },
[EXTERNALLY_INSTALLED]: {
target: EXTERNALLY_INSTALLED,
},
}, },
}, },
[NOT_INSTALLABLE]: { [NOT_INSTALLABLE]: {
......
# frozen_string_literal: true
class Admin::Clusters::IntegrationsController < Clusters::IntegrationsController
include EnforcesAdminAuthentication
private
def clusterable
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end
end
...@@ -60,6 +60,9 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -60,6 +60,9 @@ class Clusters::ClustersController < Clusters::BaseController
end end
def show def show
if params[:tab] == 'integrations'
@prometheus_integration = Clusters::IntegrationPresenter.new(@cluster.find_or_build_application(Clusters::Applications::Prometheus))
end
end end
def update def update
......
# frozen_string_literal: true
module Clusters
class IntegrationsController < ::Clusters::BaseController
before_action :cluster
before_action :authorize_admin_cluster!, only: [:create_or_update]
def create_or_update
service_response = Clusters::Integrations::CreateService
.new(container: clusterable, cluster: cluster, current_user: current_user, params: cluster_integration_params)
.execute
if service_response.success?
redirect_to cluster.show_path(params: { tab: 'integrations' }), notice: service_response.message
else
redirect_to cluster.show_path(params: { tab: 'integrations' }), alert: service_response.message
end
end
private
def clusterable
raise NotImplementedError
end
def cluster_integration_params
params.require(:integration).permit(:application_type, :enabled)
end
def cluster
@cluster ||= clusterable.clusters.find(params[:cluster_id]).present(current_user: current_user)
end
end
end
# frozen_string_literal: true
class Groups::Clusters::IntegrationsController < Clusters::IntegrationsController
include ControllerWithCrossProjectAccessCheck
prepend_before_action :group
requires_cross_project_access
private
def clusterable
@clusterable ||= ClusterablePresenter.fabricate(group, current_user: current_user)
end
def group
@group ||= find_routable!(Group, params[:group_id] || params[:id])
end
end
# frozen_string_literal: true
class Projects::Clusters::IntegrationsController < ::Clusters::IntegrationsController
prepend_before_action :project
private
def clusterable
@clusterable ||= ClusterablePresenter.fabricate(project, current_user: current_user)
end
def project
@project ||= find_routable!(Project, File.join(params[:namespace_id], params[:project_id]))
end
end
...@@ -71,6 +71,8 @@ module ClustersHelper ...@@ -71,6 +71,8 @@ module ClustersHelper
render_if_exists 'clusters/clusters/health' render_if_exists 'clusters/clusters/health'
when 'apps' when 'apps'
render 'applications' render 'applications'
when 'integrations'
render 'integrations'
when 'settings' when 'settings'
render 'advanced_settings_container' render 'advanced_settings_container'
else else
......
...@@ -32,7 +32,7 @@ module Clusters ...@@ -32,7 +32,7 @@ module Clusters
end end
state_machine :status do state_machine :status do
after_transition any => [:installed] do |application| after_transition any => [:installed, :externally_installed] do |application|
application.run_after_commit do application.run_after_commit do
Clusters::Applications::ActivateServiceWorker Clusters::Applications::ActivateServiceWorker
.perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass .perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
......
...@@ -9,6 +9,7 @@ module Clusters ...@@ -9,6 +9,7 @@ module Clusters
scope :available, -> do scope :available, -> do
where( where(
status: [ status: [
self.state_machines[:status].states[:externally_installed].value,
self.state_machines[:status].states[:installed].value, self.state_machines[:status].states[:installed].value,
self.state_machines[:status].states[:updated].value self.state_machines[:status].states[:updated].value
] ]
...@@ -28,6 +29,7 @@ module Clusters ...@@ -28,6 +29,7 @@ module Clusters
state :uninstalling, value: 7 state :uninstalling, value: 7
state :uninstall_errored, value: 8 state :uninstall_errored, value: 8
state :uninstalled, value: 10 state :uninstalled, value: 10
state :externally_installed, value: 11
# Used for applications that are pre-installed by the cluster, # Used for applications that are pre-installed by the cluster,
# e.g. Knative in GCP Cloud Run enabled clusters # e.g. Knative in GCP Cloud Run enabled clusters
...@@ -37,7 +39,7 @@ module Clusters ...@@ -37,7 +39,7 @@ module Clusters
state :pre_installed, value: 9 state :pre_installed, value: 9
event :make_externally_installed do event :make_externally_installed do
transition any => :installed transition any => :externally_installed
end end
event :make_externally_uninstalled do event :make_externally_uninstalled do
...@@ -79,7 +81,7 @@ module Clusters ...@@ -79,7 +81,7 @@ module Clusters
transition [:scheduled] => :uninstalling transition [:scheduled] => :uninstalling
end end
before_transition any => [:scheduled, :installed, :uninstalled] do |application, _| before_transition any => [:scheduled, :installed, :uninstalled, :externally_installed] do |application, _|
application.status_reason = nil application.status_reason = nil
end end
...@@ -114,7 +116,7 @@ module Clusters ...@@ -114,7 +116,7 @@ module Clusters
end end
def available? def available?
pre_installed? || installed? || updated? pre_installed? || installed? || externally_installed? || updated?
end end
def update_in_progress? def update_in_progress?
......
...@@ -437,7 +437,7 @@ module Issuable ...@@ -437,7 +437,7 @@ module Issuable
end end
def subscribed_without_subscriptions?(user, project) def subscribed_without_subscriptions?(user, project)
participants(user).include?(user) participant?(user)
end end
def can_assign_epic?(user) def can_assign_epic?(user)
......
...@@ -56,18 +56,34 @@ module Participable ...@@ -56,18 +56,34 @@ module Participable
# This method processes attributes of objects in breadth-first order. # This method processes attributes of objects in breadth-first order.
# #
# Returns an Array of User instances. # Returns an Array of User instances.
def participants(current_user = nil) def participants(user = nil)
all_participants[current_user] filtered_participants_hash[user]
end
# Checks if the user is a participant in a discussion.
#
# This method processes attributes of objects in breadth-first order.
#
# Returns a Boolean.
def participant?(user)
can_read_participable?(user) &&
all_participants_hash[user].include?(user)
end end
private private
def all_participants def all_participants_hash
@all_participants ||= Hash.new do |hash, user| @all_participants_hash ||= Hash.new do |hash, user|
hash[user] = raw_participants(user) hash[user] = raw_participants(user)
end end
end end
def filtered_participants_hash
@filtered_participants_hash ||= Hash.new do |hash, user|
hash[user] = filter_by_ability(all_participants_hash[user])
end
end
def raw_participants(current_user = nil) def raw_participants(current_user = nil)
current_user ||= author current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user) ext = Gitlab::ReferenceExtractor.new(project, current_user)
...@@ -98,8 +114,6 @@ module Participable ...@@ -98,8 +114,6 @@ module Participable
end end
participants.merge(ext.users) participants.merge(ext.users)
filter_by_ability(participants)
end end
def filter_by_ability(participants) def filter_by_ability(participants)
...@@ -110,6 +124,15 @@ module Participable ...@@ -110,6 +124,15 @@ module Participable
Ability.users_that_can_read_project(participants.to_a, project) Ability.users_that_can_read_project(participants.to_a, project)
end end
end end
def can_read_participable?(participant)
case self
when PersonalSnippet
participant.can?(:read_snippet, self)
else
participant.can?(:read_project, project)
end
end
end end
Participable.prepend_if_ee('EE::Participable') Participable.prepend_if_ee('EE::Participable')
...@@ -49,13 +49,25 @@ module Clusters ...@@ -49,13 +49,25 @@ module Clusters
end end
end end
def show_path def show_path(params: {})
if cluster.project_type? if cluster.project_type?
project_cluster_path(project, cluster) project_cluster_path(project, cluster, params)
elsif cluster.group_type? elsif cluster.group_type?
group_cluster_path(group, cluster) group_cluster_path(group, cluster, params)
elsif cluster.instance_type? elsif cluster.instance_type?
admin_cluster_path(cluster) admin_cluster_path(cluster, params)
else
raise NotImplementedError
end
end
def integrations_path
if cluster.project_type?
create_or_update_project_cluster_integration_path(project, cluster)
elsif cluster.group_type?
create_or_update_group_cluster_integration_path(group, cluster)
elsif cluster.instance_type?
create_or_update_admin_cluster_integration_path(cluster)
else else
raise NotImplementedError raise NotImplementedError
end end
......
# frozen_string_literal: true
module Clusters
class IntegrationPresenter < Gitlab::View::Presenter::Delegated
presents :integration
def integration?
integration.new_record? || integration.externally_installed? || integration.uninstalled?
end
def application_type
integration.class.application_name
end
def enabled
integration.externally_installed?
end
end
end
# frozen_string_literal: true
module Clusters
module Integrations
class CreateService < BaseContainerService
InvalidApplicationError = Class.new(StandardError)
attr_accessor :cluster
def initialize(container:, cluster:, current_user: nil, params: {})
@cluster = cluster
super(container: container, current_user: current_user, params: params)
end
def execute
return ServiceResponse.error(message: 'Unauthorized') unless authorized?
application_class = Clusters::Cluster::APPLICATIONS[params[:application_type]]
application = cluster.find_or_build_application(application_class)
if params[:enabled]
application.make_externally_installed!
ServiceResponse.success(message: s_('ClusterIntegration|Integration enabled'), payload: { application: application })
else
application.make_externally_uninstalled!
ServiceResponse.success(message: s_('ClusterIntegration|Integration disabled'), payload: { application: application })
end
end
private
def authorized?
Ability.allowed?(current_user, :admin_cluster, cluster)
end
end
end
end
.settings.expanded.border-0.m-0
%p
= s_('ClusterIntegration|Integrations enable you to integrate your cluster as part of your GitLab workflow.')
= link_to _('Learn more'), help_page_path('user/clusters/integrations.md'), target: '_blank'
.settings-content#advanced-settings-section
- if can?(current_user, :admin_cluster, @cluster) && @prometheus_integration.integration?
.sub-section.form-group
= form_for @prometheus_integration, url: @cluster.integrations_path, as: :integration, method: :post, html: { class: 'js-cluster-integrations-form' } do |form|
= form.hidden_field :application_type
.form-group
.gl-form-checkbox.custom-control.custom-checkbox
= form.check_box :enabled, { class: 'custom-control-input'}, true, false
= form.label :enabled, s_('ClusterIntegration|Enable Prometheus integration'), class: 'custom-control-label'
.gl-form-group
.form-text.text-gl-muted
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path("user/clusters/integrations", anchor: "prometheus-cluster-integration") }
- link_end = '</a>'.html_safe
= html_escape(s_('ClusterIntegration|Before you enable this integration, follow the %{link_start}documented process%{link_end}.')) % { link_start: link_start, link_end: link_end }
= form.submit _('Save changes'), class: 'btn gl-button btn-success'
- tab_name = 'integrations'
- active = params[:tab] == tab_name
%li.nav-item{ role: 'presentation' }
%a#cluster-apps-tab.nav-link{ class: active_when(active), href: clusterable.cluster_path(@cluster.id, params: {tab: tab_name}) }
%span= _('Integrations')
...@@ -59,6 +59,7 @@ ...@@ -59,6 +59,7 @@
= render_if_exists 'clusters/clusters/environments_tab' = render_if_exists 'clusters/clusters/environments_tab'
= render 'clusters/clusters/health_tab' = render 'clusters/clusters/health_tab'
= render 'applications_tab' = render 'applications_tab'
= render 'integrations_tab'
= render 'advanced_settings_tab' = render 'advanced_settings_tab'
.tab-content.py-3 .tab-content.py-3
......
---
title: Add spent quick action alias
merge_request: 58539
author: Lee Tickett @leetickett
type: added
---
title: Ability to add Prometheus as cluster integration
merge_request: 55244
author:
type: added
---
title: Check access only for requesting user when checking if subscribed
merge_request: 57201
author:
type: performance
...@@ -220,6 +220,12 @@ Rails.application.routes.draw do ...@@ -220,6 +220,12 @@ Rails.application.routes.draw do
post :authorize_aws_role post :authorize_aws_role
end end
resource :integration, controller: 'clusters/integrations', only: [] do
collection do
post :create_or_update
end
end
member do member do
Gitlab.ee do Gitlab.ee do
get :metrics, format: :json get :metrics, format: :json
......
...@@ -1001,8 +1001,8 @@ Logs produced by pods running **GitLab Managed Apps** can be browsed using ...@@ -1001,8 +1001,8 @@ Logs produced by pods running **GitLab Managed Apps** can be browsed using
## Install with one click ## Install with one click
WARNING: WARNING:
The one click installation method is scheduled for removal in GitLab 14.0. The removal The one-click installation method is deprecated and scheduled for removal in [GitLab 14.0](https://gitlab.com/groups/gitlab-org/-/epics/4280).
of this feature from GitLab does not affect installed applications to avoid breaking This removal does not affect installed applications to avoid breaking
changes. Following GitLab 14.0, users can take ownership of already installed applications changes. Following GitLab 14.0, users can take ownership of already installed applications
using our documentation. using our documentation.
......
---
stage: Configure
group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Cluster integrations **(FREE)**
GitLab provides several ways to integrate applications to your
Kubernetes cluster.
To enable cluster integrations, first add a Kubernetes cluster to a GitLab
[project](../project/clusters/add_remove_clusters.md) or [group](../group/clusters/index.md#group-level-kubernetes-clusters).
## Prometheus cluster integration
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55244) in GitLab 13.11.
You can integrate your Kubernetes cluster with
[Prometheus](https://prometheus.io/) for monitoring key metrics of your
apps directly from the GitLab UI.
Once enabled, you will see metrics from services available in the
[metrics library](../project/integrations/prometheus_library/index.md).
Prerequisites:
To benefit from this integration, you must have Prometheus
installed in your cluster with the following requirements:
1. Prometheus must be installed inside the `gitlab-managed-apps` namespace.
1. The `Service` resource for Prometheus must be named `prometheus-prometheus-server`.
You can use the following commands to install Prometheus to meet the requirements for cluster integrations:
```shell
# Create the require Kubernetes namespace
kubectl create ns gitlab-managed-apps
# Download Helm chart values that is compatible with the requirements above.
# You should substitute the tag that corresponds to the GitLab version in the url
# - https://gitlab.com/gitlab-org/gitlab/-/raw/<tag>/vendor/prometheus/values.yaml
#
wget https://gitlab.com/gitlab-org/gitlab/-/raw/v13.9.0-ee/vendor/prometheus/values.yaml
# Add the Prometheus community helm repo
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# Install Prometheus
helm install prometheus prometheus-community/prometheus -n gitlab-managed-apps --values values.yaml
```
Alternatively, you can use your preferred installation method to install
Prometheus as long as you meet the requirements above.
### Enable Prometheus integration for your cluster
To enable the Prometheus integration for your cluster:
1. Go to the cluster's page:
- For a [project-level cluster](../project/clusters/index.md), navigate to your project's
**Operations > Kubernetes**.
- For a [group-level cluster](../group/clusters/index.md), navigate to your group's
**Kubernetes** page.
1. Select the **Integrations** tab.
1. Check the **Enable Prometheus integration** checkbox.
1. Click **Save changes**.
1. Go to the **Health** tab to see your cluster's metrics.
...@@ -31,6 +31,10 @@ Once enabled, GitLab detects metrics from known services in the ...@@ -31,6 +31,10 @@ Once enabled, GitLab detects metrics from known services in the
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28916) in GitLab 10.5. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/28916) in GitLab 10.5.
**Deprecated:** Managed Prometheus on Kubernetes is deprecated, and
scheduled for removal in [GitLab
14.0](https://gitlab.com/groups/gitlab-org/-/epics/4280).
GitLab can seamlessly deploy and manage Prometheus on a GitLab can seamlessly deploy and manage Prometheus on a
[connected Kubernetes cluster](../clusters/index.md), to help you monitor your apps. [connected Kubernetes cluster](../clusters/index.md), to help you monitor your apps.
......
...@@ -10,5 +10,12 @@ module EE ...@@ -10,5 +10,12 @@ module EE
Ability.users_that_can_read_group(participants.to_a, self.group) Ability.users_that_can_read_group(participants.to_a, self.group)
end end
override :can_read_participable?
def can_read_participable?(participant)
return super unless self.is_a?(Epic)
participant.can?(:read_group, group)
end
end end
end end
...@@ -115,25 +115,6 @@ module EE ...@@ -115,25 +115,6 @@ module EE
issues.preload(project: [:route, { namespace: [:route] }]) issues.preload(project: [:route, { namespace: [:route] }])
end end
# override
def subscribed_without_subscriptions?(user, *)
# TODO: this really shouldn't be necessary, because the support
# bot should be a participant (which is what the superclass
# method checks for). However, the support bot gets filtered out
# at the end of Participable#raw_participants as not being able
# to read the project. Overriding *that* behavior is problematic
# because it doesn't use the Policy framework, and instead uses a
# custom-coded Ability.users_that_can_read_project, which is...
# a pain to override in EE. So... here we say, the support bot
# is subscribed by default, until an unsubscribed record appears,
# even though it's not *technically* a participant in this issue.
# Making the support bot subscribed to every issue is not as bad as it
# seems, though, since it isn't permitted to :receive_notifications,
# and doesn't actually show up in the participants list.
user.bot? || super
end
# override # override
def weight def weight
super if weight_available? super if weight_available?
......
...@@ -112,13 +112,13 @@ RSpec.describe Security::FindingsFinder do ...@@ -112,13 +112,13 @@ RSpec.describe Security::FindingsFinder do
subject { finder_result.total_pages } subject { finder_result.total_pages }
context 'when the per_page is not provided' do context 'when the per_page is not provided' do
it { is_expected.to be(2) } it { is_expected.to be(1) }
end end
context 'when the per_page is provided' do context 'when the per_page is provided' do
let(:per_page) { 100 } let(:per_page) { 3 }
it { is_expected.to be(1) } it { is_expected.to be(3) }
end end
end end
...@@ -126,13 +126,13 @@ RSpec.describe Security::FindingsFinder do ...@@ -126,13 +126,13 @@ RSpec.describe Security::FindingsFinder do
subject { finder_result.total_count } subject { finder_result.total_count }
context 'when the scope is not provided' do context 'when the scope is not provided' do
it { is_expected.to be(35) } it { is_expected.to be(8) }
end end
context 'when the scope is provided as `all`' do context 'when the scope is provided as `all`' do
let(:scope) { 'all' } let(:scope) { 'all' }
it { is_expected.to be(36) } it { is_expected.to be(8) }
end end
end end
...@@ -140,6 +140,9 @@ RSpec.describe Security::FindingsFinder do ...@@ -140,6 +140,9 @@ RSpec.describe Security::FindingsFinder do
subject { finder_result.next_page } subject { finder_result.next_page }
context 'when the page is not provided' do context 'when the page is not provided' do
# Limit per_page to force pagination on smaller dataset
let(:per_page) { 2 }
it { is_expected.to be(2) } it { is_expected.to be(2) }
end end
...@@ -159,6 +162,8 @@ RSpec.describe Security::FindingsFinder do ...@@ -159,6 +162,8 @@ RSpec.describe Security::FindingsFinder do
context 'when the page is provided' do context 'when the page is provided' do
let(:page) { 2 } let(:page) { 2 }
# Limit per_page to force pagination on smaller dataset
let(:per_page) { 2 }
it { is_expected.to be(1) } it { is_expected.to be(1) }
end end
...@@ -172,24 +177,12 @@ RSpec.describe Security::FindingsFinder do ...@@ -172,24 +177,12 @@ RSpec.describe Security::FindingsFinder do
%w[ %w[
4ae096451135db224b9e16818baaca8096896522 4ae096451135db224b9e16818baaca8096896522
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f 0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
117590fc6b3841014366f335f494d1aa36ce7b46 157f362acf654c60e224400f59a088e1c01b369f
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
4e44f4045e2a27d147d08895acf8df502f440f96
b5f82291ed084fe134af5a9b90a8078ab802a6cc
98366a28fa80b23a1dafe2b36e239a04909495c4
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509 b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
cefacf9f36c487d04f33c59f22e6c402bff5300a baf3e36cda35331daed7a3e80155533d552844fa
d533c3a12403b6c6033a50b53f9c73f894a40fc6 3204893d5894c74aaee86ce5bc28427f9f14e512
92c7bdc63a9908bddbc5b66c95e93e99a1927879 98366a28fa80b23a1dafe2b36e239a04909495c4
dd482eab94e695ae85c1a883c4dbe4c74a7e6b2c 9a644ee1b89ac29d6175dc1170914f47b0531635
be6f6e4fb5bdfd8819e70d930b32798b38a361e0
f603dd8517800823df02a8f1e5621b56c00710d8
21b17b6ced16fe507dd5b71bca24f0515d04fb7e
f1dde46676cd2a8e48f0837e5dae77087419b09c
fec8863c5c1b4ed58eddf7722a9f1598af3aca70
e325e114daf41074d41d1ebe1869158c4f7594dc
] ]
end end
...@@ -198,23 +191,12 @@ RSpec.describe Security::FindingsFinder do ...@@ -198,23 +191,12 @@ RSpec.describe Security::FindingsFinder do
context 'when the page is provided' do context 'when the page is provided' do
let(:page) { 2 } let(:page) { 2 }
# Limit per_page to force pagination on smaller dataset
let(:per_page) { 2 }
let(:expected_fingerprints) do let(:expected_fingerprints) do
%w[ %w[
51026f8933c463b316c5bc33adb462e4a6f6cff2 0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
45cb4c0323b0b4a1adcb66fa1d0684d53e15cc27 baf3e36cda35331daed7a3e80155533d552844fa
48f71ab14afcf0f497fb238dc4289294b93873b0
18fe6882cdac0f3eac7784a33c9daf20109010ce
2cae57e97785a8aef9ae4ed947093d6a908bcc52
857969b55ba97d5e1c06ab920b470b009c2f3274
e3b452f63d8979e6f3e4839c6ec14b62917758e4
63dfc168b8c01a446088c9b8cf68a7d4a2a0013b
7b0792ce8db4e2cb74083490e6a87176accea102
30ab265fb9e816976b740beb0557ca79e8653bb6
81a3b7c4885e64f9013ac904bf118a05bcb7732d
ecd3b645971fc2682f5cb23d938037c6f072207f
55c41a63d2c9c3ea243b9f9cd3254d68fbee2b6b
3204893d5894c74aaee86ce5bc28427f9f14e512
157f362acf654c60e224400f59a088e1c01b369f
] ]
end end
...@@ -222,44 +204,10 @@ RSpec.describe Security::FindingsFinder do ...@@ -222,44 +204,10 @@ RSpec.describe Security::FindingsFinder do
end end
context 'when the per_page is provided' do context 'when the per_page is provided' do
let(:per_page) { 40 } let(:per_page) { 1 }
let(:expected_fingerprints) do let(:expected_fingerprints) do
%w[ %w[
3204893d5894c74aaee86ce5bc28427f9f14e512
157f362acf654c60e224400f59a088e1c01b369f
4ae096451135db224b9e16818baaca8096896522 4ae096451135db224b9e16818baaca8096896522
d533c3a12403b6c6033a50b53f9c73f894a40fc6
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
98366a28fa80b23a1dafe2b36e239a04909495c4
b5f82291ed084fe134af5a9b90a8078ab802a6cc
4e44f4045e2a27d147d08895acf8df502f440f96
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
117590fc6b3841014366f335f494d1aa36ce7b46
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
92c7bdc63a9908bddbc5b66c95e93e99a1927879
cefacf9f36c487d04f33c59f22e6c402bff5300a
dd482eab94e695ae85c1a883c4dbe4c74a7e6b2c
48f71ab14afcf0f497fb238dc4289294b93873b0
45cb4c0323b0b4a1adcb66fa1d0684d53e15cc27
e3b452f63d8979e6f3e4839c6ec14b62917758e4
857969b55ba97d5e1c06ab920b470b009c2f3274
63dfc168b8c01a446088c9b8cf68a7d4a2a0013b
7b0792ce8db4e2cb74083490e6a87176accea102
2cae57e97785a8aef9ae4ed947093d6a908bcc52
18fe6882cdac0f3eac7784a33c9daf20109010ce
e325e114daf41074d41d1ebe1869158c4f7594dc
51026f8933c463b316c5bc33adb462e4a6f6cff2
fec8863c5c1b4ed58eddf7722a9f1598af3aca70
f1dde46676cd2a8e48f0837e5dae77087419b09c
21b17b6ced16fe507dd5b71bca24f0515d04fb7e
be6f6e4fb5bdfd8819e70d930b32798b38a361e0
f603dd8517800823df02a8f1e5621b56c00710d8
30ab265fb9e816976b740beb0557ca79e8653bb6
81a3b7c4885e64f9013ac904bf118a05bcb7732d
55c41a63d2c9c3ea243b9f9cd3254d68fbee2b6b
ecd3b645971fc2682f5cb23d938037c6f072207f
] ]
end end
...@@ -270,18 +218,10 @@ RSpec.describe Security::FindingsFinder do ...@@ -270,18 +218,10 @@ RSpec.describe Security::FindingsFinder do
let(:severity_levels) { [:medium] } let(:severity_levels) { [:medium] }
let(:expected_fingerprints) do let(:expected_fingerprints) do
%w[ %w[
b5f82291ed084fe134af5a9b90a8078ab802a6cc
4e44f4045e2a27d147d08895acf8df502f440f96
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
117590fc6b3841014366f335f494d1aa36ce7b46
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f 0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
d533c3a12403b6c6033a50b53f9c73f894a40fc6 9a644ee1b89ac29d6175dc1170914f47b0531635
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509 b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
98366a28fa80b23a1dafe2b36e239a04909495c4 baf3e36cda35331daed7a3e80155533d552844fa
92c7bdc63a9908bddbc5b66c95e93e99a1927879
cefacf9f36c487d04f33c59f22e6c402bff5300a
] ]
end end
...@@ -292,10 +232,7 @@ RSpec.describe Security::FindingsFinder do ...@@ -292,10 +232,7 @@ RSpec.describe Security::FindingsFinder do
let(:confidence_levels) { [:low] } let(:confidence_levels) { [:low] }
let(:expected_fingerprints) do let(:expected_fingerprints) do
%w[ %w[
30ab265fb9e816976b740beb0557ca79e8653bb6 98366a28fa80b23a1dafe2b36e239a04909495c4
81a3b7c4885e64f9013ac904bf118a05bcb7732d
55c41a63d2c9c3ea243b9f9cd3254d68fbee2b6b
ecd3b645971fc2682f5cb23d938037c6f072207f
] ]
end end
...@@ -321,25 +258,13 @@ RSpec.describe Security::FindingsFinder do ...@@ -321,25 +258,13 @@ RSpec.describe Security::FindingsFinder do
let(:expected_fingerprints) do let(:expected_fingerprints) do
%w[ %w[
4ae096451135db224b9e16818baaca8096896522 4ae096451135db224b9e16818baaca8096896522
157f362acf654c60e224400f59a088e1c01b369f
baf3e36cda35331daed7a3e80155533d552844fa
0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f 0bfcfbb70b15a7cecef9a1ea39df15ecfd88949f
117590fc6b3841014366f335f494d1aa36ce7b46
8fac98c156431a8bdb7a69a935cc564c314ab776
95566733fc91301623055363a77124410592af7e
0314c9673160662292cfab1af6dc5c880fb73717
4e44f4045e2a27d147d08895acf8df502f440f96
b5f82291ed084fe134af5a9b90a8078ab802a6cc
98366a28fa80b23a1dafe2b36e239a04909495c4 98366a28fa80b23a1dafe2b36e239a04909495c4
b9c0d1cdc7cb9c180ebb6981abbddc2df0172509 b9c0d1cdc7cb9c180ebb6981abbddc2df0172509
cefacf9f36c487d04f33c59f22e6c402bff5300a 3204893d5894c74aaee86ce5bc28427f9f14e512
d533c3a12403b6c6033a50b53f9c73f894a40fc6 9a644ee1b89ac29d6175dc1170914f47b0531635
92c7bdc63a9908bddbc5b66c95e93e99a1927879
dd482eab94e695ae85c1a883c4dbe4c74a7e6b2c
be6f6e4fb5bdfd8819e70d930b32798b38a361e0
f603dd8517800823df02a8f1e5621b56c00710d8
db759283b7fb13eae48a3f60db4c7506cdab8f26
21b17b6ced16fe507dd5b71bca24f0515d04fb7e
f1dde46676cd2a8e48f0837e5dae77087419b09c
fec8863c5c1b4ed58eddf7722a9f1598af3aca70
] ]
end end
......
...@@ -262,7 +262,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do ...@@ -262,7 +262,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
subject { described_class.new(pipeline: pipeline).execute } subject { described_class.new(pipeline: pipeline).execute }
it 'returns all vulnerabilities with all scanners available' do it 'returns all vulnerabilities with all scanners available' do
expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bandit bundler_audit find_sec_bugs flawfinder gemnasium klar zaproxy] expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bundler_audit find_sec_bugs gemnasium klar zaproxy]
end end
end end
...@@ -277,11 +277,11 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do ...@@ -277,11 +277,11 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
context 'by all filters' do context 'by all filters' do
context 'with found entity' do context 'with found entity' do
let(:params) { { report_type: %w[sast dast container_scanning dependency_scanning], scanner: %w[bandit bundler_audit find_sec_bugs flawfinder gemnasium klar zaproxy], scope: 'all' } } let(:params) { { report_type: %w[sast dast container_scanning dependency_scanning], scanner: %w[bundler_audit find_sec_bugs gemnasium klar zaproxy], scope: 'all' } }
it 'filters by all params' do it 'filters by all params' do
expect(subject.findings.count).to eq(cs_count + dast_count + ds_count + sast_count) expect(subject.findings.count).to eq(cs_count + dast_count + ds_count + sast_count)
expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bandit bundler_audit find_sec_bugs flawfinder gemnasium klar zaproxy] expect(subject.findings.map(&:scanner).map(&:external_id).uniq).to match_array %w[bundler_audit find_sec_bugs gemnasium klar zaproxy]
expect(subject.findings.map(&:confidence).uniq).to match_array(%w[unknown low medium high]) expect(subject.findings.map(&:confidence).uniq).to match_array(%w[unknown low medium high])
expect(subject.findings.map(&:severity).uniq).to match_array(%w[unknown low medium high critical info]) expect(subject.findings.map(&:severity).uniq).to match_array(%w[unknown low medium high critical info])
end end
...@@ -326,7 +326,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do ...@@ -326,7 +326,7 @@ RSpec.describe Security::PipelineVulnerabilitiesFinder do
let(:confirmed_fingerprint) do let(:confirmed_fingerprint) do
Digest::SHA1.hexdigest( Digest::SHA1.hexdigest(
'python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108') 'groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY')
end end
let(:resolved_fingerprint) do let(:resolved_fingerprint) do
......
...@@ -11,9 +11,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -11,9 +11,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
let(:created_at) { 2.weeks.ago } let(:created_at) { 2.weeks.ago }
context "when parsing valid reports" do context "when parsing valid reports" do
where(:report_format, :scanner_length) do where(:report_format, :report_version, :scanner_length, :finding_length, :identifier_length, :file_path, :line) do
:sast | 4 :sast | '14.0.0' | 1 | 5 | 6 | 'groovy/src/main/java/com/gitlab/security_products/tests/App.groovy' | 47
:sast_deprecated | 3 :sast_deprecated | '1.2' | 3 | 33 | 17 | 'python/hardcoded/hardcoded-tmp.py' | 1
end end
with_them do with_them do
...@@ -25,8 +25,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -25,8 +25,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
end end
it "parses all identifiers and findings" do it "parses all identifiers and findings" do
expect(report.findings.length).to eq(33) expect(report.findings.length).to eq(finding_length)
expect(report.identifiers.length).to eq(17) expect(report.identifiers.length).to eq(identifier_length)
expect(report.scanners.length).to eq(scanner_length) expect(report.scanners.length).to eq(scanner_length)
end end
...@@ -35,16 +35,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -35,16 +35,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::Sast) expect(location).to be_a(::Gitlab::Ci::Reports::Security::Locations::Sast)
expect(location).to have_attributes( expect(location).to have_attributes(
file_path: 'python/hardcoded/hardcoded-tmp.py', file_path: file_path,
start_line: 1, end_line: line,
end_line: 1, start_line: line
class_name: nil,
method_name: nil
) )
end end
it "generates expected metadata_version" do it "generates expected metadata_version" do
expect(report.findings.first.metadata_version).to eq('1.2') expect(report.findings.first.metadata_version).to eq(report_version)
end end
end end
end end
......
...@@ -226,7 +226,7 @@ RSpec.describe Ci::Build do ...@@ -226,7 +226,7 @@ RSpec.describe Ci::Build do
it 'parses blobs and add the results to the report' do it 'parses blobs and add the results to the report' do
subject subject
expect(security_reports.get_report('sast', artifact).findings.size).to eq(33) expect(security_reports.get_report('sast', artifact).findings.size).to eq(5)
end end
it 'adds the created date to the report' do it 'adds the created date to the report' do
...@@ -245,7 +245,7 @@ RSpec.describe Ci::Build do ...@@ -245,7 +245,7 @@ RSpec.describe Ci::Build do
it 'parses blobs and adds the results to the reports' do it 'parses blobs and adds the results to the reports' do
subject subject
expect(security_reports.get_report('sast', sast_artifact).findings.size).to eq(33) expect(security_reports.get_report('sast', sast_artifact).findings.size).to eq(5)
expect(security_reports.get_report('dependency_scanning', ds_artifact).findings.size).to eq(4) expect(security_reports.get_report('dependency_scanning', ds_artifact).findings.size).to eq(4)
expect(security_reports.get_report('container_scanning', cs_artifact).findings.size).to eq(8) expect(security_reports.get_report('container_scanning', cs_artifact).findings.size).to eq(8)
expect(security_reports.get_report('dast', dast_artifact).findings.size).to eq(20) expect(security_reports.get_report('dast', dast_artifact).findings.size).to eq(20)
......
...@@ -137,7 +137,7 @@ RSpec.describe Ci::Pipeline do ...@@ -137,7 +137,7 @@ RSpec.describe Ci::Pipeline do
expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning', 'container_scanning') expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning', 'container_scanning')
# for each of report categories, we have merged 2 reports with the same data (fixture) # for each of report categories, we have merged 2 reports with the same data (fixture)
expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(33) expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(5)
expect(subject.get_report('dependency_scanning', ds1_artifact).findings.size).to eq(4) expect(subject.get_report('dependency_scanning', ds1_artifact).findings.size).to eq(4)
expect(subject.get_report('container_scanning', cs1_artifact).findings.size).to eq(8) expect(subject.get_report('container_scanning', cs1_artifact).findings.size).to eq(8)
end end
...@@ -146,7 +146,7 @@ RSpec.describe Ci::Pipeline do ...@@ -146,7 +146,7 @@ RSpec.describe Ci::Pipeline do
let(:build_sast_1) { create(:ci_build, :retried, name: 'sast_1', pipeline: pipeline, project: project) } let(:build_sast_1) { create(:ci_build, :retried, name: 'sast_1', pipeline: pipeline, project: project) }
it 'does not take retried builds into account' do it 'does not take retried builds into account' do
expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(33) expect(subject.get_report('sast', sast1_artifact).findings.size).to eq(5)
expect(subject.get_report('dependency_scanning', ds1_artifact).findings.size).to eq(4) expect(subject.get_report('dependency_scanning', ds1_artifact).findings.size).to eq(4)
expect(subject.get_report('container_scanning', cs1_artifact).findings.size).to eq(8) expect(subject.get_report('container_scanning', cs1_artifact).findings.size).to eq(8)
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::Participable do
context 'participable is an epic' do
let(:model) { Epic }
let(:instance) { model.new }
let(:user1) { build(:user) }
let(:user2) { build(:user) }
let(:user3) { build(:user) }
let(:group) { build(:group, :public) }
before do
allow(model).to receive(:participant_attrs).and_return([:foo, :bar])
end
describe '#participants' do
it 'returns the list of participants' do
expect(instance).to receive(:foo).and_return(user2)
expect(instance).to receive(:bar).and_return(user3)
expect(instance).to receive(:group).and_return(group)
participants = instance.participants(user1)
expect(participants).to contain_exactly(user2, user3)
end
end
describe '#participant?' do
it 'returns whether the user is a participant' do
allow(instance).to receive(:foo).and_return(user2)
allow(instance).to receive(:bar).and_return(user3)
allow(instance).to receive(:group).and_return(group)
expect(instance.participant?(user1)).to be false
expect(instance.participant?(user2)).to be true
expect(instance.participant?(user3)).to be true
end
end
end
end
...@@ -227,7 +227,7 @@ RSpec.describe Ci::JobArtifact do ...@@ -227,7 +227,7 @@ RSpec.describe Ci::JobArtifact do
subject(:findings_count) { security_report.findings.length } subject(:findings_count) { security_report.findings.length }
it { is_expected.to be(33) } it { is_expected.to be(5) }
context 'for different types' do context 'for different types' do
where(:file_type, :security_report?) do where(:file_type, :security_report?) do
......
...@@ -64,7 +64,7 @@ RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportFindings' do ...@@ -64,7 +64,7 @@ RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportFindings' do
end end
it 'returns all the vulnerability findings' do it 'returns all the vulnerability findings' do
expect(security_report_findings.length).to eq(53) expect(security_report_findings.length).to eq(25)
end end
it 'returns all the queried fields', :aggregate_failures do it 'returns all the queried fields', :aggregate_failures do
......
...@@ -78,7 +78,7 @@ RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportSummary' do ...@@ -78,7 +78,7 @@ RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportSummary' do
it 'shows the vulnerabilitiesCount and scannedResourcesCount' do it 'shows the vulnerabilitiesCount and scannedResourcesCount' do
expect(security_report_summary.dig('dast', 'vulnerabilitiesCount')).to eq(20) expect(security_report_summary.dig('dast', 'vulnerabilitiesCount')).to eq(20)
expect(security_report_summary.dig('dast', 'scannedResourcesCount')).to eq(26) expect(security_report_summary.dig('dast', 'scannedResourcesCount')).to eq(26)
expect(security_report_summary.dig('sast', 'vulnerabilitiesCount')).to eq(33) expect(security_report_summary.dig('sast', 'vulnerabilitiesCount')).to eq(5)
end end
it 'shows the first 20 scanned resources' do it 'shows the first 20 scanned resources' do
......
...@@ -197,7 +197,7 @@ RSpec.describe Ci::CompareSecurityReportsService do ...@@ -197,7 +197,7 @@ RSpec.describe Ci::CompareSecurityReportsService do
it 'reports new vulnerabilities' do it 'reports new vulnerabilities' do
expect(subject[:status]).to eq(:parsed) expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['added'].count).to eq(33) expect(subject[:data]['added'].count).to eq(5)
expect(subject[:data]['fixed'].count).to eq(0) expect(subject[:data]['fixed'].count).to eq(0)
end end
end end
...@@ -218,13 +218,13 @@ RSpec.describe Ci::CompareSecurityReportsService do ...@@ -218,13 +218,13 @@ RSpec.describe Ci::CompareSecurityReportsService do
it 'reports new vulnerability' do it 'reports new vulnerability' do
expect(subject[:data]['added'].count).to eq(1) expect(subject[:data]['added'].count).to eq(1)
expect(subject[:data]['added'].first['identifiers']).to include(a_hash_including('name' => 'CWE-120')) expect(subject[:data]['added'].first['identifiers']).to include(a_hash_including('name' => 'CWE-327'))
end end
it 'reports fixed sast vulnerabilities' do it 'reports fixed sast vulnerabilities' do
expect(subject[:data]['fixed'].count).to eq(4) expect(subject[:data]['fixed'].count).to eq(1)
compare_keys = collect_ids(subject[:data]['fixed']) compare_keys = collect_ids(subject[:data]['fixed'])
expected_keys = %w(char fopen strcpy char) expected_keys = %w(CIPHER_INTEGRITY)
expect(compare_keys - expected_keys).to eq([]) expect(compare_keys - expected_keys).to eq([])
end end
end end
......
...@@ -129,7 +129,7 @@ RSpec.describe Security::ReportSummaryService, '#execute' do ...@@ -129,7 +129,7 @@ RSpec.describe Security::ReportSummaryService, '#execute' do
it 'returns the vulnerability count' do it 'returns the vulnerability count' do
expect(result).to match(a_hash_including( expect(result).to match(a_hash_including(
dast: a_hash_including(vulnerabilities_count: 20), dast: a_hash_including(vulnerabilities_count: 20),
sast: a_hash_including(vulnerabilities_count: 33), sast: a_hash_including(vulnerabilities_count: 5),
container_scanning: a_hash_including(vulnerabilities_count: 8), container_scanning: a_hash_including(vulnerabilities_count: 8),
dependency_scanning: a_hash_including(vulnerabilities_count: 4) dependency_scanning: a_hash_including(vulnerabilities_count: 4)
)) ))
......
...@@ -31,7 +31,7 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -31,7 +31,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
where(:case_name, :trait, :scanners, :identifiers, :findings, :finding_identifiers, :finding_pipelines, :remediations, :fingerprints) do where(:case_name, :trait, :scanners, :identifiers, :findings, :finding_identifiers, :finding_pipelines, :remediations, :fingerprints) do
'with SAST report' | :sast | 3 | 17 | 33 | 39 | 33 | 0 | 2 'with SAST report' | :sast | 1 | 6 | 5 | 7 | 5 | 0 | 2
'with exceeding identifiers' | :with_exceeding_identifiers | 1 | 20 | 1 | 20 | 1 | 0 | 0 'with exceeding identifiers' | :with_exceeding_identifiers | 1 | 20 | 1 | 20 | 1 | 0 | 0
'with Dependency Scanning report' | :dependency_scanning_remediation | 1 | 3 | 2 | 3 | 2 | 1 | 0 'with Dependency Scanning report' | :dependency_scanning_remediation | 1 | 3 | 2 | 3 | 2 | 1 | 0
'with Container Scanning report' | :container_scanning | 1 | 8 | 8 | 8 | 8 | 0 | 0 'with Container Scanning report' | :container_scanning | 1 | 8 | 8 | 8 | 8 | 0 | 0
...@@ -113,9 +113,13 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -113,9 +113,13 @@ RSpec.describe Security::StoreReportService, '#execute' do
end end
context 'with existing data from previous pipeline' do context 'with existing data from previous pipeline' do
let(:scanner) { build(:vulnerabilities_scanner, project: project, external_id: 'bandit', name: 'Bandit') } let(:finding_identifier_fingerprint) do
let(:identifier) { build(:vulnerabilities_identifier, project: project, fingerprint: 'e6dd15eda2137be0034977a85b300a94a4f243a3') } build(:ci_reports_security_identifier, external_id: "CIPHER_INTEGRITY").fingerprint
let(:different_identifier) { build(:vulnerabilities_identifier, project: project, fingerprint: 'fa47ee81f079e5c38ea6edb700b44eaeb62f67ee') } end
let(:scanner) { build(:vulnerabilities_scanner, project: project, external_id: 'find_sec_bugs', name: 'Find Security Bugs') }
let(:identifier) { build(:vulnerabilities_identifier, project: project, fingerprint: finding_identifier_fingerprint) }
let(:different_identifier) { build(:vulnerabilities_identifier, project: project) }
let!(:new_artifact) { create(:ee_ci_job_artifact, :sast, job: new_build) } let!(:new_artifact) { create(:ee_ci_job_artifact, :sast, job: new_build) }
let(:new_build) { create(:ci_build, pipeline: new_pipeline) } let(:new_build) { create(:ci_build, pipeline: new_pipeline) }
let(:new_pipeline) { create(:ci_pipeline, project: project) } let(:new_pipeline) { create(:ci_pipeline, project: project) }
...@@ -129,6 +133,15 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -129,6 +133,15 @@ RSpec.describe Security::StoreReportService, '#execute' do
let(:trait) { :sast } let(:trait) { :sast }
let(:finding_location_fingerprint) do
build(
:ci_reports_security_locations_sast,
file_path: "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
start_line: "29",
end_line: "29"
).fingerprint
end
let!(:finding) do let!(:finding) do
create(:vulnerabilities_finding, create(:vulnerabilities_finding,
pipelines: [pipeline], pipelines: [pipeline],
...@@ -136,8 +149,8 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -136,8 +149,8 @@ RSpec.describe Security::StoreReportService, '#execute' do
primary_identifier: identifier, primary_identifier: identifier,
scanner: scanner, scanner: scanner,
project: project, project: project,
uuid: "80571acf-8660-4bc8-811a-1d8dec9ab6f4", uuid: "e5388f40-18f5-566d-95c6-d64c6f46a00a",
location_fingerprint: 'd869ba3f0b3347eb2749135a437dc07c8ae0f420') location_fingerprint: finding_location_fingerprint)
end end
let!(:vulnerability) { create(:vulnerability, findings: [finding], project: project) } let!(:vulnerability) { create(:vulnerability, findings: [finding], project: project) }
...@@ -180,30 +193,30 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -180,30 +193,30 @@ RSpec.describe Security::StoreReportService, '#execute' do
expect(finding.reload.uuid).to eq(desired_uuid) expect(finding.reload.uuid).to eq(desired_uuid)
end end
it 'inserts only new scanners and reuse existing ones' do it 'reuses existing scanner' do
expect { subject }.to change { Vulnerabilities::Scanner.count }.by(2) expect { subject }.not_to change { Vulnerabilities::Scanner.count }
end end
it 'inserts only new identifiers and reuse existing ones' do it 'inserts only new identifiers and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Identifier.count }.by(16) expect { subject }.to change { Vulnerabilities::Identifier.count }.by(5)
end end
it 'inserts only new findings and reuse existing ones' do it 'inserts only new findings and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Finding.count }.by(32) expect { subject }.to change { Vulnerabilities::Finding.count }.by(4)
end end
it 'inserts all finding pipelines (join model) for this new pipeline' do it 'inserts all finding pipelines (join model) for this new pipeline' do
expect { subject }.to change { Vulnerabilities::FindingPipeline.where(pipeline: new_pipeline).count }.by(33) expect { subject }.to change { Vulnerabilities::FindingPipeline.where(pipeline: new_pipeline).count }.by(5)
end end
it 'inserts new vulnerabilities with data from findings from this new pipeline' do it 'inserts new vulnerabilities with data from findings from this new pipeline' do
expect { subject }.to change { Vulnerability.count }.by(32) expect { subject }.to change { Vulnerability.count }.by(4)
end end
it 'updates existing findings with new data' do it 'updates existing findings with new data' do
subject subject
expect(finding.reload).to have_attributes(severity: 'medium', name: 'Probable insecure usage of temp file/directory.') expect(finding.reload).to have_attributes(severity: 'medium', name: 'Cipher with no integrity')
end end
it 'updates fingerprints to match new values' do it 'updates fingerprints to match new values' do
...@@ -234,7 +247,7 @@ RSpec.describe Security::StoreReportService, '#execute' do ...@@ -234,7 +247,7 @@ RSpec.describe Security::StoreReportService, '#execute' do
it 'updates existing vulnerability with new data' do it 'updates existing vulnerability with new data' do
subject subject
expect(vulnerability.reload).to have_attributes(severity: 'medium', title: 'Probable insecure usage of temp file/directory.', title_html: 'Probable insecure usage of temp file/directory.') expect(vulnerability.reload).to have_attributes(severity: 'medium', title: 'Cipher with no integrity', title_html: 'Cipher with no integrity')
end end
context 'when the existing vulnerability is resolved with the latest report' do context 'when the existing vulnerability is resolved with the latest report' do
......
...@@ -26,14 +26,27 @@ RSpec.describe Security::StoreScanService do ...@@ -26,14 +26,27 @@ RSpec.describe Security::StoreScanService do
describe '#execute' do describe '#execute' do
let_it_be(:unique_finding_uuid) { artifact.security_report.findings[0].uuid } let_it_be(:unique_finding_uuid) { artifact.security_report.findings[0].uuid }
let_it_be(:duplicate_finding_uuid) { artifact.security_report.findings[5].uuid } let_it_be(:duplicate_finding_uuid) { artifact.security_report.findings[4].uuid }
let(:finding_location_fingerprint) do
build(
:ci_reports_security_locations_sast,
file_path: "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
start_line: "41",
end_line: "41"
).fingerprint
end
let(:finding_identifier_fingerprint) do
build(:ci_reports_security_identifier, external_id: "PREDICTABLE_RANDOM").fingerprint
end
let(:deduplicate) { false } let(:deduplicate) { false }
let(:service_object) { described_class.new(artifact, known_keys, deduplicate) } let(:service_object) { described_class.new(artifact, known_keys, deduplicate) }
let(:finding_key) do let(:finding_key) do
build(:ci_reports_security_finding_key, build(:ci_reports_security_finding_key,
location_fingerprint: 'd869ba3f0b3347eb2749135a437dc07c8ae0f420', location_fingerprint: finding_location_fingerprint,
identifier_fingerprint: 'e6dd15eda2137be0034977a85b300a94a4f243a3') identifier_fingerprint: finding_identifier_fingerprint)
end end
subject(:store_scan) { service_object.execute } subject(:store_scan) { service_object.execute }
......
...@@ -64,7 +64,7 @@ RSpec.describe Security::VulnerabilityCountingService, '#execute' do ...@@ -64,7 +64,7 @@ RSpec.describe Security::VulnerabilityCountingService, '#execute' do
end end
it { it {
is_expected.to match(a_hash_including("sast" => 33, is_expected.to match(a_hash_including("sast" => 5,
"dast" => 20, "dast" => 20,
"container_scanning" => 8, "container_scanning" => 8,
"dependency_scanning" => 4)) "dependency_scanning" => 4))
......
...@@ -182,7 +182,7 @@ module Gitlab ...@@ -182,7 +182,7 @@ module Gitlab
parse_params do |raw_time_date| parse_params do |raw_time_date|
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
end end
command :spend do |time_spent, time_spent_date| command :spend, :spent do |time_spent, time_spent_date|
if time_spent if time_spent
@updates[:spend_time] = { @updates[:spend_time] = {
duration: time_spent, duration: time_spent,
......
...@@ -28,7 +28,7 @@ module Gitlab ...@@ -28,7 +28,7 @@ module Gitlab
'unassign_reviewer' 'unassign_reviewer'
when 'request_review', 'reviewer' when 'request_review', 'reviewer'
'assign_reviewer' 'assign_reviewer'
when 'spend' when 'spend', 'spent'
event_name_for_spend(args) event_name_for_spend(args)
when 'unassign' when 'unassign'
event_name_for_unassign(args) event_name_for_unassign(args)
......
...@@ -6690,6 +6690,9 @@ msgstr "" ...@@ -6690,6 +6690,9 @@ msgstr ""
msgid "ClusterIntegration|Base domain" msgid "ClusterIntegration|Base domain"
msgstr "" msgstr ""
msgid "ClusterIntegration|Before you enable this integration, follow the %{link_start}documented process%{link_end}."
msgstr ""
msgid "ClusterIntegration|Blocking mode" msgid "ClusterIntegration|Blocking mode"
msgstr "" msgstr ""
...@@ -6855,6 +6858,9 @@ msgstr "" ...@@ -6855,6 +6858,9 @@ msgstr ""
msgid "ClusterIntegration|Enable Cloud Run for Anthos" msgid "ClusterIntegration|Enable Cloud Run for Anthos"
msgstr "" msgstr ""
msgid "ClusterIntegration|Enable Prometheus integration"
msgstr ""
msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster." msgid "ClusterIntegration|Enable or disable GitLab's connection to your Kubernetes cluster."
msgstr "" msgstr ""
...@@ -6993,6 +6999,15 @@ msgstr "" ...@@ -6993,6 +6999,15 @@ msgstr ""
msgid "ClusterIntegration|Integrate with a cluster certificate" msgid "ClusterIntegration|Integrate with a cluster certificate"
msgstr "" msgstr ""
msgid "ClusterIntegration|Integration disabled"
msgstr ""
msgid "ClusterIntegration|Integration enabled"
msgstr ""
msgid "ClusterIntegration|Integrations enable you to integrate your cluster as part of your GitLab workflow."
msgstr ""
msgid "ClusterIntegration|Issuer Email" msgid "ClusterIntegration|Issuer Email"
msgstr "" msgstr ""
...@@ -12807,6 +12822,9 @@ msgstr "" ...@@ -12807,6 +12822,9 @@ msgstr ""
msgid "ExternalWikiService|The URL of the external wiki" msgid "ExternalWikiService|The URL of the external wiki"
msgstr "" msgstr ""
msgid "Externally installed"
msgstr ""
msgid "Facebook" msgid "Facebook"
msgstr "" msgstr ""
......
...@@ -546,20 +546,30 @@ RSpec.describe Admin::ClustersController do ...@@ -546,20 +546,30 @@ RSpec.describe Admin::ClustersController do
describe 'GET #show' do describe 'GET #show' do
let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } let(:cluster) { create(:cluster, :provided_by_gcp, :instance) }
def get_show def get_show(tab: nil)
get :show, get :show,
params: { params: {
id: cluster id: cluster,
tab: tab
} }
end end
describe 'functionality' do describe 'functionality' do
render_views
it 'responds successfully' do it 'responds successfully' do
get_show get_show
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster) expect(assigns(:cluster)).to eq(cluster)
end end
it 'renders integration tab view' do
get_show(tab: 'integrations')
expect(response).to render_template('clusters/clusters/_integrations')
expect(response).to have_gitlab_http_status(:ok)
end
end end
describe 'security' do describe 'security' do
......
...@@ -641,21 +641,31 @@ RSpec.describe Groups::ClustersController do ...@@ -641,21 +641,31 @@ RSpec.describe Groups::ClustersController do
describe 'GET show' do describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) } let(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :group_type, groups: [group]) }
def go def go(tab: nil)
get :show, get :show,
params: { params: {
group_id: group, group_id: group,
id: cluster id: cluster,
tab: tab
} }
end end
describe 'functionality' do describe 'functionality' do
render_views
it 'renders view' do it 'renders view' do
go go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster) expect(assigns(:cluster)).to eq(cluster)
end end
it 'renders integration tab view', :aggregate_failures do
go(tab: 'integrations')
expect(response).to render_template('clusters/clusters/_integrations')
expect(response).to have_gitlab_http_status(:ok)
end
end end
describe 'security' do describe 'security' do
......
...@@ -674,22 +674,32 @@ RSpec.describe Projects::ClustersController do ...@@ -674,22 +674,32 @@ RSpec.describe Projects::ClustersController do
describe 'GET show' do describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
def go def go(tab: nil)
get :show, get :show,
params: { params: {
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
id: cluster id: cluster,
tab: tab
} }
end end
describe 'functionality' do describe 'functionality' do
render_views
it "renders view" do it "renders view" do
go go
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:cluster)).to eq(cluster) expect(assigns(:cluster)).to eq(cluster)
end end
it 'renders integration tab view' do
go(tab: 'integrations')
expect(response).to render_template('clusters/clusters/_integrations')
expect(response).to have_gitlab_http_status(:ok)
end
end end
describe 'security' do describe 'security' do
......
...@@ -69,6 +69,10 @@ FactoryBot.define do ...@@ -69,6 +69,10 @@ FactoryBot.define do
status { 10 } status { 10 }
end end
trait :externally_installed do
status { 11 }
end
trait :timed_out do trait :timed_out do
installing installing
updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago }
......
...@@ -89,6 +89,12 @@ describe('Application Row', () => { ...@@ -89,6 +89,12 @@ describe('Application Row', () => {
checkButtonState('Install', false, true); checkButtonState('Install', false, true);
}); });
it('has disabled "Externally installed" when APPLICATION_STATUS.EXTERNALLY_INSTALLED', () => {
mountComponent({ status: APPLICATION_STATUS.EXTERNALLY_INSTALLED });
checkButtonState('Externally installed', false, true);
});
it('has disabled "Installed" when application is installed and not uninstallable', () => { it('has disabled "Installed" when application is installed and not uninstallable', () => {
mountComponent({ mountComponent({
status: APPLICATION_STATUS.INSTALLED, status: APPLICATION_STATUS.INSTALLED,
......
...@@ -20,6 +20,8 @@ const { ...@@ -20,6 +20,8 @@ const {
UNINSTALLING, UNINSTALLING,
UNINSTALL_ERRORED, UNINSTALL_ERRORED,
UNINSTALLED, UNINSTALLED,
PRE_INSTALLED,
EXTERNALLY_INSTALLED,
} = APPLICATION_STATUS; } = APPLICATION_STATUS;
const NO_EFFECTS = 'no effects'; const NO_EFFECTS = 'no effects';
...@@ -29,19 +31,21 @@ describe('applicationStateMachine', () => { ...@@ -29,19 +31,21 @@ describe('applicationStateMachine', () => {
describe(`current state is ${NO_STATUS}`, () => { describe(`current state is ${NO_STATUS}`, () => {
it.each` it.each`
expectedState | event | effects expectedState | event | effects
${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS} ${INSTALLING} | ${SCHEDULED} | ${NO_EFFECTS}
${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS} ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS} ${INSTALLING} | ${INSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS}
${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }} ${INSTALLABLE} | ${ERROR} | ${{ installFailed: true }}
${UPDATING} | ${UPDATING} | ${NO_EFFECTS} ${UPDATING} | ${UPDATING} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATED} | ${NO_EFFECTS} ${INSTALLED} | ${UPDATED} | ${NO_EFFECTS}
${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }} ${INSTALLED} | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS} ${UNINSTALLING} | ${UNINSTALLING} | ${NO_EFFECTS}
${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }} ${INSTALLED} | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
${UNINSTALLED} | ${UNINSTALLED} | ${NO_EFFECTS} ${UNINSTALLED} | ${UNINSTALLED} | ${NO_EFFECTS}
${PRE_INSTALLED} | ${PRE_INSTALLED} | ${NO_EFFECTS}
${EXTERNALLY_INSTALLED} | ${EXTERNALLY_INSTALLED} | ${NO_EFFECTS}
`(`transitions to $expectedState on $event event and applies $effects`, (data) => { `(`transitions to $expectedState on $event event and applies $effects`, (data) => {
const { expectedState, event, effects } = data; const { expectedState, event, effects } = data;
const currentAppState = { const currentAppState = {
......
...@@ -115,6 +115,26 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle ...@@ -115,6 +115,26 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle
end end
end end
context 'tracking spent' do
let(:quickaction_name) { 'spent' }
context 'adding time' do
let(:args) { '1d' }
it_behaves_like 'a tracked quick action unique event' do
let(:action) { 'i_quickactions_spend_add' }
end
end
context 'removing time' do
let(:args) { '-1d' }
it_behaves_like 'a tracked quick action unique event' do
let(:action) { 'i_quickactions_spend_subtract' }
end
end
end
context 'tracking unassign' do context 'tracking unassign' do
let(:quickaction_name) { 'unassign' } let(:quickaction_name) { 'unassign' }
......
...@@ -39,6 +39,19 @@ RSpec.describe Clusters::Applications::Prometheus do ...@@ -39,6 +39,19 @@ RSpec.describe Clusters::Applications::Prometheus do
end end
end end
describe 'transition to externally_installed' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm) }
let(:application) { create(:clusters_applications_prometheus, :installing, cluster: cluster) }
it 'schedules post installation job' do
expect(Clusters::Applications::ActivateServiceWorker)
.to receive(:perform_async).with(cluster.id, 'prometheus')
application.make_externally_installed!
end
end
describe 'transition to updating' do describe 'transition to updating' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:cluster) { create(:cluster, projects: [project]) } let(:cluster) { create(:cluster, projects: [project]) }
......
...@@ -380,7 +380,7 @@ RSpec.describe Issuable do ...@@ -380,7 +380,7 @@ RSpec.describe Issuable do
context 'user is a participant in the issue' do context 'user is a participant in the issue' do
before do before do
allow(issue).to receive(:participants).with(user).and_return([user]) allow(issue).to receive(:participant?).with(user).and_return(true)
end end
it 'returns false when no subcription exists' do it 'returns false when no subcription exists' do
......
...@@ -39,11 +39,12 @@ RSpec.describe Participable do ...@@ -39,11 +39,12 @@ RSpec.describe Participable do
expect(participants).to include(user3) expect(participants).to include(user3)
end end
it 'caches the raw list of participants' do it 'caches the list of filtered participants' do
instance = model.new instance = model.new
user1 = build(:user) user1 = build(:user)
expect(instance).to receive(:raw_participants).once expect(instance).to receive(:all_participants_hash).once.and_return({})
expect(instance).to receive(:filter_by_ability).once
instance.participants(user1) instance.participants(user1)
instance.participants(user1) instance.participants(user1)
...@@ -91,5 +92,71 @@ RSpec.describe Participable do ...@@ -91,5 +92,71 @@ RSpec.describe Participable do
expect(ext_arg).to be_an_instance_of(Gitlab::ReferenceExtractor) expect(ext_arg).to be_an_instance_of(Gitlab::ReferenceExtractor)
end end
end end
context 'participable is a personal snippet' do
let(:model) { PersonalSnippet }
let(:instance) { model.new(author: user1) }
let(:user1) { build(:user) }
let(:user2) { build(:user) }
let(:user3) { build(:user) }
before do
allow(model).to receive(:participant_attrs).and_return([:foo, :bar])
end
it 'returns the list of participants' do
expect(instance).to receive(:foo).and_return(user1)
expect(instance).to receive(:bar).and_return(user2)
participants = instance.participants(user1)
expect(participants).to contain_exactly(user1)
end
end
end
describe '#participant?' do
let(:instance) { model.new }
let(:user1) { build(:user) }
let(:user2) { build(:user) }
let(:user3) { build(:user) }
let(:project) { build(:project, :public) }
before do
allow(model).to receive(:participant_attrs).and_return([:foo, :bar])
end
it 'returns whether the user is a participant' do
allow(instance).to receive(:foo).and_return(user2)
allow(instance).to receive(:bar).and_return(user3)
allow(instance).to receive(:project).and_return(project)
expect(instance.participant?(user1)).to be false
expect(instance.participant?(user2)).to be true
expect(instance.participant?(user3)).to be true
end
it 'caches the list of raw participants' do
expect(instance).to receive(:raw_participants).once.and_return([])
expect(instance).to receive(:project).twice.and_return(project)
instance.participant?(user1)
instance.participant?(user1)
end
context 'participable is a personal snippet' do
let(:model) { PersonalSnippet }
let(:instance) { model.new(author: user1) }
it 'returns whether the user is a participant' do
allow(instance).to receive(:foo).and_return(user1)
allow(instance).to receive(:bar).and_return(user2)
expect(instance.participant?(user1)).to be true
expect(instance.participant?(user2)).to be false
expect(instance.participant?(user3)).to be false
end
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::Clusters::IntegrationsController, :enable_admin_mode do
include AccessMatchersForController
shared_examples 'a secure endpoint' do
context 'it is allowed for admins only' do
it { expect { subject }.to be_allowed_for(:admin) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
end
end
describe 'POST create_or_update' do
let(:cluster) { create(:cluster, :instance, :provided_by_gcp) }
let(:user) { create(:admin) }
it_behaves_like '#create_or_update action' do
let(:path) { create_or_update_admin_cluster_integration_path(cluster) }
let(:redirect_path) { admin_cluster_path(cluster, params: { tab: 'integrations' }) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::Clusters::IntegrationsController do
include AccessMatchersForController
shared_examples 'a secure endpoint' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { subject }.to be_allowed_for(:admin)
end
it 'is denied for admin when admin mode disabled' do
expect { subject }.to be_denied_for(:admin)
end
context 'it is allowed for group maintainers' do
it { expect { subject }.to be_allowed_for(:owner).of(group) }
it { expect { subject }.to be_allowed_for(:maintainer).of(group) }
it { expect { subject }.to be_denied_for(:developer).of(group) }
it { expect { subject }.to be_denied_for(:reporter).of(group) }
it { expect { subject }.to be_denied_for(:guest).of(group) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
end
end
describe 'POST create_or_update' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
let_it_be(:member) { create(:group_member, user: user, group: group) }
let(:cluster) { create(:cluster, :group, :provided_by_gcp, groups: [group]) }
it_behaves_like '#create_or_update action' do
let(:path) { create_or_update_group_cluster_integration_path(group, cluster) }
let(:redirect_path) { group_cluster_path(group, cluster, params: { tab: 'integrations' }) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Clusters::IntegrationsController do
include AccessMatchersForController
shared_examples 'a secure endpoint' do
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
expect { subject }.to be_allowed_for(:admin)
end
it 'is denied for admin when admin mode disabled' do
expect { subject }.to be_denied_for(:admin)
end
context 'it is allowed for project maintainers' do
it { expect { subject }.to be_allowed_for(:owner).of(project) }
it { expect { subject }.to be_allowed_for(:maintainer).of(project) }
it { expect { subject }.to be_denied_for(:developer).of(project) }
it { expect { subject }.to be_denied_for(:reporter).of(project) }
it { expect { subject }.to be_denied_for(:guest).of(project) }
it { expect { subject }.to be_denied_for(:user) }
it { expect { subject }.to be_denied_for(:external) }
end
end
describe 'POST create_or_update' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
let(:user) { project.owner }
it_behaves_like '#create_or_update action' do
let(:path) { create_or_update_project_cluster_integration_path(project, cluster) }
let(:redirect_path) { project_cluster_path(project, cluster, params: { tab: 'integrations' }) }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Integrations::CreateService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let(:params) do
{ application_type: 'prometheus', enabled: true }
end
let(:service) do
described_class.new(container: project, cluster: cluster, current_user: project.owner, params: params)
end
it 'creates a new Prometheus instance' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_externally_installed
end
context 'enabled param is false' do
let(:params) do
{ application_type: 'prometheus', enabled: false }
end
it 'creates a new uninstalled Prometheus instance' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_uninstalled
end
end
context 'unauthorized user' do
let(:service) do
unauthorized_user = create(:user)
described_class.new(container: project, cluster: cluster, current_user: unauthorized_user, params: params)
end
it 'does not create a new Prometheus instance' do
expect(service.execute).to be_error
expect(cluster.application_prometheus).to be_nil
end
end
context 'prometheus record exists' do
before do
create(:clusters_applications_prometheus, cluster: cluster)
end
it 'updates the Prometheus instance' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_externally_installed
end
context 'enabled param is false' do
let(:params) do
{ application_type: 'prometheus', enabled: false }
end
it 'updates the Prometheus instance as uninstalled' do
expect(service.execute).to be_success
expect(cluster.application_prometheus).to be_present
expect(cluster.application_prometheus).to be_persisted
expect(cluster.application_prometheus).to be_uninstalled
end
end
end
context 'for an un-supported application type' do
let(:params) do
{ application_type: 'something_else', enabled: true }
end
it 'errors' do
expect { service.execute}.to raise_error(ArgumentError)
end
end
end
...@@ -368,24 +368,24 @@ RSpec.describe QuickActions::InterpretService do ...@@ -368,24 +368,24 @@ RSpec.describe QuickActions::InterpretService do
spent_at: DateTime.current.to_date spent_at: DateTime.current.to_date
}) })
end end
it 'returns the spend_time message including the formatted duration and verb' do
_, _, message = service.execute('/spend -120m', issuable)
expect(message).to eq('Subtracted 2h spent time.')
end
end end
shared_examples 'spend command with negative time' do shared_examples 'spend command with negative time' do
it 'populates spend_time: -1800 if content contains /spend -30m' do it 'populates spend_time: -7200 if content contains -120m' do
_, updates, _ = service.execute(content, issuable) _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: { expect(updates).to eq(spend_time: {
duration: -1800, duration: -7200,
user_id: developer.id, user_id: developer.id,
spent_at: DateTime.current.to_date spent_at: DateTime.current.to_date
}) })
end end
it 'returns the spend_time message including the formatted duration and verb' do
_, _, message = service.execute(content, issuable)
expect(message).to eq('Subtracted 2h spent time.')
end
end end
shared_examples 'spend command with valid date' do shared_examples 'spend command with valid date' do
...@@ -1242,8 +1242,18 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1242,8 +1242,18 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'spend command' do
let(:content) { '/spent 1h' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with negative time' do it_behaves_like 'spend command with negative time' do
let(:content) { '/spend -30m' } let(:content) { '/spend -120m' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with negative time' do
let(:content) { '/spent -120m' }
let(:issuable) { issue } let(:issuable) { issue }
end end
...@@ -1253,26 +1263,52 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1253,26 +1263,52 @@ RSpec.describe QuickActions::InterpretService do
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'spend command with valid date' do
let(:date) { '2016-02-02' }
let(:content) { "/spent 30m #{date}" }
let(:issuable) { issue }
end
it_behaves_like 'spend command with invalid date' do it_behaves_like 'spend command with invalid date' do
let(:content) { '/spend 30m 17-99-99' } let(:content) { '/spend 30m 17-99-99' }
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'spend command with invalid date' do
let(:content) { '/spent 30m 17-99-99' }
let(:issuable) { issue }
end
it_behaves_like 'spend command with future date' do it_behaves_like 'spend command with future date' do
let(:content) { '/spend 30m 6017-10-10' } let(:content) { '/spend 30m 6017-10-10' }
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'spend command with future date' do
let(:content) { '/spent 30m 6017-10-10' }
let(:issuable) { issue }
end
it_behaves_like 'failed command' do it_behaves_like 'failed command' do
let(:content) { '/spend' } let(:content) { '/spend' }
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'failed command' do
let(:content) { '/spent' }
let(:issuable) { issue }
end
it_behaves_like 'failed command' do it_behaves_like 'failed command' do
let(:content) { '/spend abc' } let(:content) { '/spend abc' }
let(:issuable) { issue } let(:issuable) { issue }
end end
it_behaves_like 'failed command' do
let(:content) { '/spent abc' }
let(:issuable) { issue }
end
it_behaves_like 'remove_estimate command' do it_behaves_like 'remove_estimate command' do
let(:content) { '/remove_estimate' } let(:content) { '/remove_estimate' }
let(:issuable) { issue } let(:issuable) { issue }
...@@ -2247,10 +2283,14 @@ RSpec.describe QuickActions::InterpretService do ...@@ -2247,10 +2283,14 @@ RSpec.describe QuickActions::InterpretService do
end end
describe 'spend command' do describe 'spend command' do
let(:content) { '/spend -120m' } it 'includes the formatted duration and proper verb when using /spend' do
_, explanations = service.explain('/spend -120m', issue)
it 'includes the formatted duration and proper verb' do expect(explanations).to eq(['Subtracts 2h spent time.'])
_, explanations = service.explain(content, issue) end
it 'includes the formatted duration and proper verb when using /spent' do
_, explanations = service.explain('/spent -120m', issue)
expect(explanations).to eq(['Subtracts 2h spent time.']) expect(explanations).to eq(['Subtracts 2h spent time.'])
end end
......
...@@ -138,7 +138,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -138,7 +138,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do it 'is installed' do
subject.make_externally_installed subject.make_externally_installed
expect(subject).to be_installed expect(subject).to be_externally_installed
end end
context 'helm record does not exist' do context 'helm record does not exist' do
...@@ -170,7 +170,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -170,7 +170,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do it 'is installed' do
subject.make_externally_installed subject.make_externally_installed
expect(subject).to be_installed expect(subject).to be_externally_installed
end end
end end
...@@ -180,7 +180,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -180,7 +180,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
it 'is installed' do it 'is installed' do
subject.make_externally_installed subject.make_externally_installed
expect(subject).to be_installed expect(subject).to be_externally_installed
end end
it 'clears #status_reason' do it 'clears #status_reason' do
...@@ -317,6 +317,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name| ...@@ -317,6 +317,7 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
:uninstall_errored | false :uninstall_errored | false
:uninstalled | false :uninstalled | false
:timed_out | false :timed_out | false
:externally_installed | true
end end
with_them do with_them do
......
# frozen_string_literal: true
RSpec.shared_examples '#create_or_update action' do
let(:params) do
{ integration: { application_type: Clusters::Applications::Prometheus.application_name, enabled: true } }
end
let(:path) { raise NotImplementedError }
let(:redirect_path) { raise NotImplementedError }
describe 'authorization' do
subject do
post path, params: params
end
it_behaves_like 'a secure endpoint'
end
describe 'functionality' do
before do
sign_in(user)
end
it 'redirects on success' do
post path, params: params
expect(response).to have_gitlab_http_status(:redirect)
expect(response).to redirect_to(redirect_path)
expect(flash[:notice]).to be_present
end
it 'redirects on error' do
error = ServiceResponse.error(message: 'failed')
expect_next_instance_of(Clusters::Integrations::CreateService) do |service|
expect(service).to receive(:execute).and_return(error)
end
post path, params: params
expect(response).to have_gitlab_http_status(:redirect)
expect(response).to redirect_to(redirect_path)
expect(flash[:alert]).to eq(error.message)
end
end
end
...@@ -41,7 +41,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name| ...@@ -41,7 +41,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
end.to change(application_class, :count) end.to change(application_class, :count)
expect(cluster_application).to be_persisted expect(cluster_application).to be_persisted
expect(cluster_application).to be_installed expect(cluster_application).to be_externally_installed
end end
end end
...@@ -53,7 +53,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name| ...@@ -53,7 +53,7 @@ RSpec.shared_examples 'parse cluster applications artifact' do |release_name|
it 'marks the application as installed' do it 'marks the application as installed' do
described_class.new(job, user).execute(artifact) described_class.new(job, user).execute(artifact)
expect(cluster_application).to be_installed expect(cluster_application).to be_externally_installed
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment