Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-19 21:08:05 +00:00
parent d84f18d66c
commit 680d188025
50 changed files with 533 additions and 292 deletions

View File

@ -19,7 +19,7 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security`
- [ ] Assign to a reviewer and maintainer, per our [Code Review process].
- [ ] For the MR targeting `master`:
- [ ] Ping appsec team member who created the issue and ask for a non-blocking review with `Please review this MR`.
- [ ] Ask for a non-blocking review from the AppSec team member associated to the issue in the [Canonical repository](https://gitlab.com/gitlab-org/gitlab). If you're unsure who to ping, ask on `#sec-appsec` Slack channel.
- [ ] Ensure it's approved according to our [Approval Guidelines].
- [ ] Merge request _must not_ close the corresponding security issue, _unless_ it targets `master`.

View File

@ -359,9 +359,6 @@ RSpec/LeakyConstantDeclaration:
- 'spec/lib/gitlab/database/obsolete_ignored_columns_spec.rb'
- 'spec/lib/gitlab/database/with_lock_retries_spec.rb'
- 'spec/lib/gitlab/git/diff_collection_spec.rb'
- 'spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb'
- 'spec/lib/gitlab/health_checks/master_check_spec.rb'
- 'spec/lib/gitlab/import_export/attribute_configuration_spec.rb'
- 'spec/lib/gitlab/import_export/import_test_coverage_spec.rb'
- 'spec/lib/gitlab/import_export/project/relation_factory_spec.rb'
- 'spec/lib/gitlab/jira_import/issues_importer_spec.rb'
@ -372,17 +369,14 @@ RSpec/LeakyConstantDeclaration:
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- 'spec/lib/gitlab/view/presenter/factory_spec.rb'
- 'spec/lib/marginalia_spec.rb'
- 'spec/lib/system_check/simple_executor_spec.rb'
- 'spec/mailers/notify_spec.rb'
- 'spec/migrations/20191125114345_add_admin_mode_protected_path_spec.rb'
- 'spec/migrations/encrypt_plaintext_attributes_on_application_settings_spec.rb'
- 'spec/models/concerns/batch_destroy_dependent_associations_spec.rb'
- 'spec/models/concerns/bulk_insert_safe_spec.rb'
- 'spec/models/concerns/bulk_insertable_associations_spec.rb'
- 'spec/models/concerns/triggerable_hooks_spec.rb'
- 'spec/models/repository_spec.rb'
- 'spec/requests/api/graphql/tasks/task_completion_status_spec.rb'
- 'spec/rubocop/cop/rspec/env_assignment_spec.rb'
- 'spec/serializers/commit_entity_spec.rb'
- 'spec/services/clusters/applications/check_installation_progress_service_spec.rb'
- 'spec/services/clusters/applications/check_uninstall_progress_service_spec.rb'

View File

@ -1,22 +1,32 @@
<script>
import { mapState, mapActions } from 'vuex';
import { GlTable, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui';
import { GlBadge, GlLink, GlLoadingIcon, GlPagination, GlTable } from '@gitlab/ui';
import tooltip from '~/vue_shared/directives/tooltip';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import { __, sprintf } from '~/locale';
export default {
components: {
GlTable,
GlBadge,
GlLink,
GlLoadingIcon,
GlBadge,
GlPagination,
GlTable,
},
directives: {
tooltip,
},
computed: {
...mapState(['clusters', 'loading']),
...mapState(['clusters', 'clustersPerPage', 'loading', 'page', 'totalCulsters']),
currentPage: {
get() {
return this.page;
},
set(newVal) {
this.setPage(newVal);
this.fetchClusters();
},
},
fields() {
return [
{
@ -47,12 +57,15 @@ export default {
},
];
},
hasClusters() {
return this.clustersPerPage > 0;
},
},
mounted() {
this.fetchClusters();
},
methods: {
...mapActions(['fetchClusters']),
...mapActions(['fetchClusters', 'setPage']),
statusClass(status) {
const iconClass = STATUSES[status] || STATUSES.default;
return iconClass.className;
@ -67,33 +80,46 @@ export default {
<template>
<gl-loading-icon v-if="loading" size="md" class="mt-3" />
<gl-table v-else :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
<template #cell(name)="{ item }">
<div class="d-flex flex-row-reverse flex-md-row js-status">
<gl-link data-qa-selector="cluster" :data-qa-cluster-name="item.name" :href="item.path">
{{ item.name }}
</gl-link>
<gl-loading-icon
v-if="item.status === 'deleting'"
v-tooltip
:title="statusTitle(item.status)"
size="sm"
class="mr-2 ml-md-2"
/>
<div
v-else
v-tooltip
class="cluster-status-indicator rounded-circle align-self-center gl-w-4 gl-h-4 mr-2 ml-md-2"
:class="statusClass(item.status)"
:title="statusTitle(item.status)"
></div>
</div>
</template>
<template #cell(cluster_type)="{value}">
<gl-badge variant="light">
{{ value }}
</gl-badge>
</template>
</gl-table>
<section v-else>
<gl-table :items="clusters" :fields="fields" stacked="md" class="qa-clusters-table">
<template #cell(name)="{ item }">
<div class="d-flex flex-row-reverse flex-md-row js-status">
<gl-link data-qa-selector="cluster" :data-qa-cluster-name="item.name" :href="item.path">
{{ item.name }}
</gl-link>
<gl-loading-icon
v-if="item.status === 'deleting'"
v-tooltip
:title="statusTitle(item.status)"
size="sm"
class="mr-2 ml-md-2"
/>
<div
v-else
v-tooltip
class="cluster-status-indicator rounded-circle align-self-center gl-w-4 gl-h-4 mr-2 ml-md-2"
:class="statusClass(item.status)"
:title="statusTitle(item.status)"
></div>
</div>
</template>
<template #cell(cluster_type)="{value}">
<gl-badge variant="light">
{{ value }}
</gl-badge>
</template>
</gl-table>
<gl-pagination
v-if="hasClusters"
v-model="currentPage"
:per-page="clustersPerPage"
:total-items="totalCulsters"
:prev-text="__('Prev')"
:next-text="__('Next')"
align="center"
/>
</section>
</template>

View File

@ -2,18 +2,22 @@ import Poll from '~/lib/utils/poll';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
import * as types from './mutation_types';
export const fetchClusters = ({ state, commit }) => {
const poll = new Poll({
resource: {
fetchClusters: endpoint => axios.get(endpoint),
fetchClusters: paginatedEndPoint => axios.get(paginatedEndPoint),
},
data: state.endpoint,
data: `${state.endpoint}?page=${state.page}`,
method: 'fetchClusters',
successCallback: ({ data }) => {
successCallback: ({ data, headers }) => {
if (data.clusters) {
commit(types.SET_CLUSTERS_DATA, data);
const normalizedHeaders = normalizeHeaders(headers);
const paginationInformation = parseIntPagination(normalizedHeaders);
commit(types.SET_CLUSTERS_DATA, { data, paginationInformation });
commit(types.SET_LOADING_STATE, false);
poll.stop();
}
@ -24,5 +28,9 @@ export const fetchClusters = ({ state, commit }) => {
poll.makeRequest();
};
export const setPage = ({ commit }, page) => {
commit(types.SET_PAGE, page);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -1,2 +1,3 @@
export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA';
export const SET_LOADING_STATE = 'SET_LOADING_STATE';
export const SET_PAGE = 'SET_PAGE';

View File

@ -4,10 +4,15 @@ export default {
[types.SET_LOADING_STATE](state, value) {
state.loading = value;
},
[types.SET_CLUSTERS_DATA](state, data) {
[types.SET_CLUSTERS_DATA](state, { data, paginationInformation }) {
Object.assign(state, {
clusters: data.clusters,
clustersPerPage: paginationInformation.perPage,
hasAncestorClusters: data.has_ancestor_clusters,
totalCulsters: paginationInformation.total,
});
},
[types.SET_PAGE](state, value) {
state.page = Number(value) || 1;
},
};

View File

@ -3,4 +3,7 @@ export default (initialState = {}) => ({
hasAncestorClusters: false,
loading: true,
clusters: [],
clustersPerPage: 0,
page: 1,
totalCulsters: 0,
});

View File

@ -1,5 +1,6 @@
<script>
import { mapActions } from 'vuex';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
@ -7,6 +8,10 @@ export default {
timeAgoTooltip,
GitlabTeamMemberBadge: () =>
import('ee_component/vue_shared/components/user_avatar/badges/gitlab_team_member_badge.vue'),
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
author: {
@ -44,6 +49,11 @@ export default {
required: false,
default: true,
},
isConfidential: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -174,6 +184,15 @@ export default {
</a>
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
</template>
<gl-icon
v-if="isConfidential"
v-gl-tooltip:tooltipcontainer.bottom
data-testid="confidentialIndicator"
name="eye-slash"
:size="14"
:title="s__('Notes|Private comments are accessible by internal staff only')"
class="gl-ml-1 gl-text-gray-800 align-middle"
/>
<slot name="extra-controls"></slot>
<i
v-if="showSpinner"

View File

@ -255,7 +255,13 @@ export default {
</div>
<div class="timeline-content">
<div class="note-header">
<note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id">
<note-header
v-once
:author="author"
:created-at="note.created_at"
:note-id="note.id"
:is-confidential="note.confidential"
>
<slot slot="note-header-info" name="note-header-info"></slot>
<span v-if="commit" v-html="actionText"></span>
<span v-else-if="note.created_at" class="d-none d-sm-inline">&middot;</span>

View File

@ -68,7 +68,7 @@ export default {
footer-primary-button-variant="warning"
@submit="onSubmit"
>
<template slot="title">
<template #title>
{{ title }}
</template>
<div>

View File

@ -316,6 +316,7 @@ class ProjectPolicy < BasePolicy
enable :update_deployment
enable :create_release
enable :update_release
enable :daily_statistics
enable :create_metrics_dashboard_annotation
enable :delete_metrics_dashboard_annotation
enable :update_metrics_dashboard_annotation
@ -358,7 +359,6 @@ class ProjectPolicy < BasePolicy
enable :create_environment_terminal
enable :destroy_release
enable :destroy_artifacts
enable :daily_statistics
enable :admin_operations
enable :read_deploy_token
enable :create_deploy_token

View File

@ -2,8 +2,32 @@
module Snippets
class BaseService < ::BaseService
include SpamCheckMethods
CreateRepositoryError = Class.new(StandardError)
attr_reader :uploaded_files
def initialize(project, user = nil, params = {})
super
@uploaded_files = Array(@params.delete(:files).presence)
filter_spam_check_params
end
private
def visibility_allowed?(snippet, visibility_level)
Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
end
def error_forbidden_visibility(snippet)
deny_visibility_level(snippet)
snippet_error_response(snippet, 403)
end
def snippet_error_response(snippet, http_status)
ServiceResponse.error(
message: snippet.errors.full_messages.to_sentence,

View File

@ -2,25 +2,11 @@
module Snippets
class CreateService < Snippets::BaseService
include SpamCheckMethods
CreateRepositoryError = Class.new(StandardError)
def execute
filter_spam_check_params
@snippet = build_from_params
@files = Array(params.delete(:files).presence)
@snippet = if project
project.snippets.build(params)
else
PersonalSnippet.new(params)
end
unless Gitlab::VisibilityLevel.allowed_for?(current_user, @snippet.visibility_level)
deny_visibility_level(@snippet)
return snippet_error_response(@snippet, 403)
unless visibility_allowed?(@snippet, @snippet.visibility_level)
return error_forbidden_visibility(@snippet)
end
@snippet.author = current_user
@ -41,6 +27,14 @@ module Snippets
private
def build_from_params
if project
project.snippets.build(params)
else
PersonalSnippet.new(params)
end
end
def save_and_commit
snippet_saved = @snippet.save
@ -91,7 +85,7 @@ module Snippets
def move_temporary_files
return unless @snippet.is_a?(PersonalSnippet)
@files.each do |file|
uploaded_files.each do |file|
FileMover.new(file, from_model: current_user, to_model: @snippet).execute
end
end

View File

@ -2,26 +2,15 @@
module Snippets
class UpdateService < Snippets::BaseService
include SpamCheckMethods
COMMITTABLE_ATTRIBUTES = %w(file_name content).freeze
UpdateError = Class.new(StandardError)
CreateRepositoryError = Class.new(StandardError)
def execute(snippet)
# check that user is allowed to set specified visibility_level
new_visibility = visibility_level
if new_visibility && new_visibility.to_i != snippet.visibility_level
unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility)
return snippet_error_response(snippet, 403)
end
if visibility_changed?(snippet) && !visibility_allowed?(snippet, visibility_level)
return error_forbidden_visibility(snippet)
end
filter_spam_check_params
snippet.assign_attributes(params)
spam_check(snippet, current_user)
@ -36,6 +25,10 @@ module Snippets
private
def visibility_changed?(snippet)
visibility_level && visibility_level.to_i != snippet.visibility_level
end
def save_and_commit(snippet)
return false unless snippet.save

View File

@ -27,7 +27,7 @@
%td
- if token.expires?
%span{ class: ('text-warning' if token.expires_soon?) }
In #{distance_of_time_in_words_to_now(token.expires_at)}
= _('In %{time_to_now}') % { time_to_now: distance_of_time_in_words_to_now(token.expires_at) }
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(', ') : _('<no scopes selected>')

View File

@ -22,14 +22,14 @@
- elsif event.target
= link_to event.target.to_reference, [event.project.namespace.becomes(Namespace), event.project, event.target], class: 'has-tooltip', title: event.target_title
at
= s_('UserProfile|at')
%strong
- if event.project
= link_to_project(event.project)
- else
= event.resource_parent_name
- else
made a private contribution
= s_('UserProfile|made a private contribution')
- else
%p
= _('No contributions were found')

View File

@ -0,0 +1,5 @@
---
title: Add confidential status support for comment and replies
merge_request: 31622
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/shared/_personal_access_tokens_table.html.haml
merge_request: 32116
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/users/calendar_activities.html.haml
merge_request: 32094
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: 'Cluster index refactor: Add missing pagination'
merge_request: 32338
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix leaky constant issue in env assignment spec
merge_request: 32040
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix leaky constant issue in application settings encrypt spec
merge_request: 32066
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix leaky constant issue in simple executor spec
merge_request: 32082
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix leaky constant issue connection, master check and attr config spec
merge_request: 32144
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Correct the permission according to docs
merge_request: 28657
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Validate package types in package metadatum models
merge_request: 32091
author: Sashi Kumar
type: other

View File

@ -0,0 +1,5 @@
---
title: Update deprecated slot syntax in ./app/assets/javascripts/pages/milestones/shared/components/promote_milestone_modal.vue
merge_request: 31980
author: Gilang Gumilar
type: other

View File

@ -62,7 +62,7 @@ The following settings are:
| Setting | Description | Default |
|---------|-------------|---------|
| `enabled` | Enable/disable object storage | `true` |
| `enabled` | Enable/disable object storage | `false` |
| `remote_directory` | The bucket name where Terraform state files will be stored | |
| `connection` | Various connection options described below | |

View File

@ -2,7 +2,7 @@
type: howto
---
# Installing GitLab HA on Amazon Web Services (AWS)
# Installing GitLab on Amazon Web Services (AWS)
This page offers a walkthrough of a common configuration
for GitLab on AWS. You should customize it to accommodate your needs.
@ -16,7 +16,7 @@ GitLab on AWS can leverage many of the services that are already
configurable. These services offer a great deal of
flexibility and can be adapted to the needs of most companies.
In this guide, we'll go through a basic HA setup where we'll start by
In this guide, we'll go through a multi-node setup where we'll start by
configuring our Virtual Private Cloud and subnets to later integrate
services such as RDS for our database server and ElastiCache as a Redis
cluster to finally manage them within an auto scaling group with custom
@ -306,7 +306,7 @@ Now, it's time to create the database:
1. Under **Settings**, set a DB instance identifier, a master username, and a master password. We'll use `gitlab-db-ha`, `gitlab`, and a very secure password respectively. Make a note of these as we'll need them later.
1. For the DB instance size, select **Standard classes** and select an instance size that meets your requirements from the dropdown menu. We'll use a `db.m4.large` instance.
1. Under **Storage**, configure the following:
1. Select **Provisioned IOPS (SSD)** from the storage type dropdown menu. Provisioned IOPS (SSD) storage is best suited for HA (though you can choose General Purpose (SSD) to reduce the costs). Read more about it at [Storage for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html).
1. Select **Provisioned IOPS (SSD)** from the storage type dropdown menu. Provisioned IOPS (SSD) storage is best suited for this use (though you can choose General Purpose (SSD) to reduce the costs). Read more about it at [Storage for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html).
1. Allocate storage and set provisioned IOPS. We'll use the minimum values, `100` and `1000`, respectively.
1. Enable storage autoscaling (optional) and set a maximum storage threshold.
1. Under **Availability & durability**, select **Create a standby instance** to have a standby RDS instance provisioned in a different [Availability Zone](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html).

View File

@ -83,7 +83,7 @@ The following table depicts the various user permission levels in a project.
| See a container registry | | ✓ | ✓ | ✓ | ✓ |
| See environments | | ✓ | ✓ | ✓ | ✓ |
| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ |
| View project statistics | | | ✓ | ✓ | ✓ |
| View project statistics | | | ✓ | ✓ | ✓ |
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| Create new merge request | | ✓ | ✓ | ✓ | ✓ |
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |

View File

@ -11589,6 +11589,9 @@ msgstr ""
msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition."
msgstr ""
msgid "In %{time_to_now}"
msgstr ""
msgid "In order to enable instance-level analytics, please ask an admin to enable %{usage_ping_link_start}usage ping%{usage_ping_link_end}."
msgstr ""
@ -14519,6 +14522,9 @@ msgstr ""
msgid "Notes|Collapse replies"
msgstr ""
msgid "Notes|Private comments are accessible by internal staff only"
msgstr ""
msgid "Notes|Show all activity"
msgstr ""
@ -14965,6 +14971,9 @@ msgstr ""
msgid "Package type must be NuGet"
msgstr ""
msgid "Package type must be PyPi"
msgstr ""
msgid "Package was removed"
msgstr ""
@ -23775,6 +23784,12 @@ msgstr ""
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
msgstr ""
msgid "UserProfile|at"
msgstr ""
msgid "UserProfile|made a private contribution"
msgstr ""
msgid "Username (optional)"
msgstr ""

View File

@ -57,6 +57,10 @@ module QA
click_element :download_export_link
end
def has_download_export_link?
has_element? :download_export_link
end
def archive_project
page.accept_alert("Are you sure that you want to archive this project?") do
click_element :archive_project_link

View File

@ -4,7 +4,7 @@ import ClusterStore from '~/clusters_list/store';
import MockAdapter from 'axios-mock-adapter';
import { apiData } from '../mock_data';
import { mount } from '@vue/test-utils';
import { GlTable, GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlTable, GlPagination } from '@gitlab/ui';
describe('Clusters', () => {
let mock;
@ -14,11 +14,12 @@ describe('Clusters', () => {
const endpoint = 'some/endpoint';
const findLoader = () => wrapper.find(GlLoadingIcon);
const findPaginatedButtons = () => wrapper.find(GlPagination);
const findTable = () => wrapper.find(GlTable);
const findStatuses = () => findTable().findAll('.js-status');
const mockPollingApi = (response, body, header) => {
mock.onGet(endpoint).reply(response, body, header);
mock.onGet(`${endpoint}?page=${header['x-page']}`).reply(response, body, header);
};
const mountWrapper = () => {
@ -29,7 +30,11 @@ describe('Clusters', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mockPollingApi(200, apiData, {});
mockPollingApi(200, apiData, {
'x-total': apiData.clusters.length,
'x-per-page': 20,
'x-page': 1,
});
return mountWrapper();
});
@ -93,4 +98,47 @@ describe('Clusters', () => {
}
});
});
describe('pagination', () => {
const perPage = apiData.clusters.length;
const totalFirstPage = 100;
const totalSecondPage = 500;
beforeEach(() => {
mockPollingApi(200, apiData, {
'x-total': totalFirstPage,
'x-per-page': perPage,
'x-page': 1,
});
return mountWrapper();
});
it('should load to page 1 with header values', () => {
const buttons = findPaginatedButtons();
expect(buttons.props('perPage')).toBe(perPage);
expect(buttons.props('totalItems')).toBe(totalFirstPage);
expect(buttons.props('value')).toBe(1);
});
describe('when updating currentPage', () => {
beforeEach(() => {
mockPollingApi(200, apiData, {
'x-total': totalSecondPage,
'x-per-page': perPage,
'x-page': 2,
});
wrapper.setData({ currentPage: 2 });
return axios.waitForAll();
});
it('should change pagination when currentPage changes', () => {
const buttons = findPaginatedButtons();
expect(buttons.props('perPage')).toBe(perPage);
expect(buttons.props('totalItems')).toBe(totalSecondPage);
expect(buttons.props('value')).toBe(2);
});
});
});
});

View File

@ -19,14 +19,29 @@ describe('Clusters store actions', () => {
afterEach(() => mock.restore());
it('should commit SET_CLUSTERS_DATA with received response', done => {
mock.onGet().reply(200, apiData);
const headers = {
'x-total': apiData.clusters.length,
'x-per-page': 20,
'x-page': 1,
};
const paginationInformation = {
nextPage: NaN,
page: 1,
perPage: 20,
previousPage: NaN,
total: apiData.clusters.length,
totalPages: NaN,
};
mock.onGet().reply(200, apiData, headers);
testAction(
actions.fetchClusters,
{ endpoint: apiData.endpoint },
{},
[
{ type: types.SET_CLUSTERS_DATA, payload: apiData },
{ type: types.SET_CLUSTERS_DATA, payload: { data: apiData, paginationInformation } },
{ type: types.SET_LOADING_STATE, payload: false },
],
[],

View File

@ -39,7 +39,7 @@ describe('Dashboard', () => {
const findEnvironmentsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' });
const findAllEnvironmentsDropdownItems = () => findEnvironmentsDropdown().findAll(GlDropdownItem);
const setSearchTerm = searchTerm => {
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm);
};
const createShallowWrapper = (props = {}, options = {}) => {
@ -135,7 +135,7 @@ describe('Dashboard', () => {
it('hides the group panels when showPanels is false', () => {
createMountedWrapper({ hasMetrics: true, showPanels: false });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.showEmptyState).toEqual(false);
@ -148,7 +148,7 @@ describe('Dashboard', () => {
createMountedWrapper({ hasMetrics: true });
wrapper.vm.$store.commit(
store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
@ -188,7 +188,7 @@ describe('Dashboard', () => {
);
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', {
@ -205,7 +205,7 @@ describe('Dashboard', () => {
setSearch('');
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).not.toHaveBeenCalledWith(
@ -228,7 +228,7 @@ describe('Dashboard', () => {
);
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(createFlash).toHaveBeenCalled();
@ -328,7 +328,7 @@ describe('Dashboard', () => {
beforeEach(() => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
@ -436,7 +436,7 @@ describe('Dashboard', () => {
it('hides the environments dropdown list when there is no environments', () => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithDashboard(wrapper.vm.$store);
setupStoreWithDashboard(store);
return wrapper.vm.$nextTick().then(() => {
expect(findAllEnvironmentsDropdownItems()).toHaveLength(0);
@ -446,7 +446,7 @@ describe('Dashboard', () => {
it('renders the datetimepicker dropdown', () => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(DateTimePicker).exists()).toBe(true);
@ -456,7 +456,7 @@ describe('Dashboard', () => {
it('renders the refresh dashboard button', () => {
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick().then(() => {
const refreshBtn = wrapper.findAll({ ref: 'refreshDashboardBtn' });
@ -469,8 +469,8 @@ describe('Dashboard', () => {
describe('variables section', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithVariable(wrapper.vm.$store);
setupStoreWithData(store);
setupStoreWithVariable(store);
return wrapper.vm.$nextTick();
});
@ -486,7 +486,7 @@ describe('Dashboard', () => {
describe('when the panel is not expanded', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
@ -524,14 +524,14 @@ describe('Dashboard', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true }, { stubs: { DashboardPanel: MockPanel } });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
const { panelGroups } = wrapper.vm.$store.state.monitoringDashboard.dashboard;
const { panelGroups } = store.state.monitoringDashboard.dashboard;
group = panelGroups[0].group;
[panel] = panelGroups[0].panels;
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, {
group,
panel,
});
@ -593,10 +593,8 @@ describe('Dashboard', () => {
beforeEach(() => {
createShallowWrapper({ hasMetrics: true });
const { $store } = wrapper.vm;
setupStoreWithDashboard($store);
setMetricResult({ $store, result: [], panel: 2 });
setupStoreWithDashboard(store);
setMetricResult({ store, result: [], panel: 2 });
return wrapper.vm.$nextTick();
});
@ -622,7 +620,7 @@ describe('Dashboard', () => {
beforeEach(() => {
createMountedWrapper({ hasMetrics: true }, { attachToDocument: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
@ -673,7 +671,7 @@ describe('Dashboard', () => {
});
it('shows loading element when environments fetch is still loading', () => {
wrapper.vm.$store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`);
return wrapper.vm
.$nextTick()
@ -681,7 +679,7 @@ describe('Dashboard', () => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownLoading' }).exists()).toBe(true);
})
.then(() => {
wrapper.vm.$store.commit(
store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
@ -703,7 +701,7 @@ describe('Dashboard', () => {
store.dispatch.mockRestore();
createShallowWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
return wrapper.vm.$nextTick();
});
@ -791,7 +789,7 @@ describe('Dashboard', () => {
createShallowWrapper({ hasMetrics: true, showHeader: false });
// all_dashboards is not defined in health dashboards
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
return wrapper.vm.$nextTick();
});
@ -917,7 +915,7 @@ describe('Dashboard', () => {
customMetricsPath: '/endpoint',
customMetricsAvailable: true,
});
setupStoreWithData(wrapper.vm.$store);
setupStoreWithData(store);
origPage = document.body.dataset.page;
document.body.dataset.page = 'projects:environments:metrics';

View File

@ -47,7 +47,7 @@ describe('Metrics dashboard/variables section component', () => {
it('shows the variables section', () => {
createShallowWrapper();
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables);
return wrapper.vm.$nextTick(() => {
const allInputs = findTextInput().length + findCustomInput().length;

View File

@ -2,48 +2,48 @@ import * as types from '~/monitoring/stores/mutation_types';
import { metricsResult, environmentData, dashboardGitResponse } from './mock_data';
import { metricsDashboardPayload } from './fixture_data';
export const setMetricResult = ({ $store, result, group = 0, panel = 0, metric = 0 }) => {
const { dashboard } = $store.state.monitoringDashboard;
export const setMetricResult = ({ store, result, group = 0, panel = 0, metric = 0 }) => {
const { dashboard } = store.state.monitoringDashboard;
const { metricId } = dashboard.panelGroups[group].panels[panel].metrics[metric];
$store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, {
store.commit(`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, {
metricId,
result,
});
};
const setEnvironmentData = $store => {
$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData);
const setEnvironmentData = store => {
store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData);
};
export const setupAllDashboards = $store => {
$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse);
export const setupAllDashboards = store => {
store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse);
};
export const setupStoreWithDashboard = $store => {
$store.commit(
export const setupStoreWithDashboard = store => {
store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
metricsDashboardPayload,
);
$store.commit(
store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DASHBOARD_SUCCESS}`,
metricsDashboardPayload,
);
};
export const setupStoreWithVariable = $store => {
$store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
export const setupStoreWithVariable = store => {
store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, {
label1: 'pod',
});
};
export const setupStoreWithData = $store => {
setupAllDashboards($store);
setupStoreWithDashboard($store);
export const setupStoreWithData = store => {
setupAllDashboards(store);
setupStoreWithDashboard(store);
setMetricResult({ $store, result: [], panel: 0 });
setMetricResult({ $store, result: metricsResult, panel: 1 });
setMetricResult({ $store, result: metricsResult, panel: 2 });
setMetricResult({ store, result: [], panel: 0 });
setMetricResult({ store, result: metricsResult, panel: 1 });
setMetricResult({ store, result: metricsResult, panel: 2 });
setEnvironmentData($store);
setEnvironmentData(store);
};

View File

@ -18,6 +18,7 @@ describe('NoteHeader component', () => {
const findActionText = () => wrapper.find({ ref: 'actionText' });
const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
const findConfidentialIndicator = () => wrapper.find('[data-testid="confidentialIndicator"]');
const findSpinner = () => wrapper.find({ ref: 'spinner' });
const author = {
@ -231,4 +232,15 @@ describe('NoteHeader component', () => {
});
});
});
describe('with confidentiality indicator', () => {
it.each`
status | condition
${true} | ${'shows'}
${false} | ${'hides'}
`('$condition icon indicator when isConfidential is $status', ({ status }) => {
createComponent({ isConfidential: status });
expect(findConfidentialIndicator().exists()).toBe(status);
});
});
});

View File

@ -9,6 +9,14 @@ describe Gitlab::Graphql::Pagination::Keyset::Connection do
let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)}
let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) }
before do
stub_const('NoPrimaryKey', Class.new(ActiveRecord::Base))
NoPrimaryKey.class_eval do
self.table_name = 'no_primary_key'
self.primary_key = nil
end
end
subject(:connection) do
described_class.new(nodes, { context: context, max_page_size: 3 }.merge(arguments))
end
@ -303,9 +311,4 @@ describe Gitlab::Graphql::Pagination::Keyset::Connection do
end
end
end
class NoPrimaryKey < ActiveRecord::Base
self.table_name = 'no_primary_key'
self.primary_key = nil
end
end

View File

@ -6,10 +6,9 @@ require_relative './simple_check_shared'
describe Gitlab::HealthChecks::MasterCheck do
let(:result_class) { Gitlab::HealthChecks::Result }
SUCCESS_CODE = 100
FAILURE_CODE = 101
before do
stub_const('SUCCESS_CODE', 100)
stub_const('FAILURE_CODE', 101)
described_class.register_master
end

View File

@ -43,7 +43,4 @@ describe 'Import/Export attribute configuration' do
IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
MSG
end
class Author < User
end
end

View File

@ -4,99 +4,109 @@ require 'spec_helper'
require 'rake_helper'
describe SystemCheck::SimpleExecutor do
class SimpleCheck < SystemCheck::BaseCheck
set_name 'my simple check'
before do
stub_const('SimpleCheck', Class.new(SystemCheck::BaseCheck))
stub_const('OtherCheck', Class.new(SystemCheck::BaseCheck))
stub_const('SkipCheck', Class.new(SystemCheck::BaseCheck))
stub_const('DynamicSkipCheck', Class.new(SystemCheck::BaseCheck))
stub_const('MultiCheck', Class.new(SystemCheck::BaseCheck))
stub_const('SkipMultiCheck', Class.new(SystemCheck::BaseCheck))
stub_const('RepairCheck', Class.new(SystemCheck::BaseCheck))
stub_const('BugousCheck', Class.new(SystemCheck::BaseCheck))
def check?
true
end
end
SimpleCheck.class_eval do
set_name 'my simple check'
class OtherCheck < SystemCheck::BaseCheck
set_name 'other check'
def check?
false
def check?
true
end
end
def show_error
$stdout.puts 'this is an error text'
end
end
OtherCheck.class_eval do
set_name 'other check'
class SkipCheck < SystemCheck::BaseCheck
set_name 'skip check'
set_skip_reason 'this is a skip reason'
def check?
false
end
def skip?
true
def show_error
$stdout.puts 'this is an error text'
end
end
def check?
raise 'should not execute this'
end
end
SkipCheck.class_eval do
set_name 'skip check'
set_skip_reason 'this is a skip reason'
class DynamicSkipCheck < SystemCheck::BaseCheck
set_name 'dynamic skip check'
set_skip_reason 'this is a skip reason'
def skip?
true
end
def skip?
self.skip_reason = 'this is a dynamic skip reason'
true
def check?
raise 'should not execute this'
end
end
def check?
raise 'should not execute this'
end
end
DynamicSkipCheck.class_eval do
set_name 'dynamic skip check'
set_skip_reason 'this is a skip reason'
class MultiCheck < SystemCheck::BaseCheck
set_name 'multi check'
def skip?
self.skip_reason = 'this is a dynamic skip reason'
true
end
def multi_check
$stdout.puts 'this is a multi output check'
def check?
raise 'should not execute this'
end
end
def check?
raise 'should not execute this'
end
end
MultiCheck.class_eval do
set_name 'multi check'
class SkipMultiCheck < SystemCheck::BaseCheck
set_name 'skip multi check'
def multi_check
$stdout.puts 'this is a multi output check'
end
def skip?
true
def check?
raise 'should not execute this'
end
end
def multi_check
raise 'should not execute this'
end
end
SkipMultiCheck.class_eval do
set_name 'skip multi check'
class RepairCheck < SystemCheck::BaseCheck
set_name 'repair check'
def skip?
true
end
def check?
false
def multi_check
raise 'should not execute this'
end
end
def repair!
true
RepairCheck.class_eval do
set_name 'repair check'
def check?
false
end
def repair!
true
end
def show_error
$stdout.puts 'this is an error message'
end
end
def show_error
$stdout.puts 'this is an error message'
end
end
BugousCheck.class_eval do
set_name 'my bugous check'
class BugousCheck < SystemCheck::BaseCheck
CustomError = Class.new(StandardError)
set_name 'my bugous check'
def check?
raise CustomError, 'omg'
def check?
raise StandardError, 'omg'
end
end
end

View File

@ -8,7 +8,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
let(:application_settings) { table(:application_settings) }
let(:plaintext) { 'secret-token' }
PLAINTEXT_ATTRIBUTES = %w[
plaintext_attributes = %w[
akismet_api_key
elasticsearch_aws_secret_access_key
recaptcha_private_key
@ -21,7 +21,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
it 'encrypts token and saves it' do
application_setting = application_settings.create
application_setting.update_columns(
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
plaintext_attributes.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = plaintext
end
)
@ -29,7 +29,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
migration.up
application_setting.reload
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
plaintext_attributes.each do |plaintext_attribute|
expect(application_setting[plaintext_attribute]).not_to be_nil
expect(application_setting["encrypted_#{plaintext_attribute}"]).not_to be_nil
expect(application_setting["encrypted_#{plaintext_attribute}_iv"]).not_to be_nil
@ -40,7 +40,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
describe '#down' do
it 'decrypts encrypted token and saves it' do
application_setting = application_settings.create(
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
plaintext_attributes.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = plaintext
end
)
@ -48,7 +48,7 @@ describe EncryptPlaintextAttributesOnApplicationSettings do
migration.down
application_setting.reload
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
plaintext_attributes.each do |plaintext_attribute|
expect(application_setting[plaintext_attribute]).to eq(plaintext)
expect(application_setting["encrypted_#{plaintext_attribute}"]).to be_nil
expect(application_setting["encrypted_#{plaintext_attribute}_iv"]).to be_nil

View File

@ -42,7 +42,7 @@ describe ProjectPolicy do
admin_tag admin_milestone admin_merge_request update_merge_request create_commit_status
update_commit_status create_build update_build create_pipeline
update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image destroy_container_image
resolve_note create_container_image update_container_image destroy_container_image daily_statistics
create_environment update_environment create_deployment update_deployment create_release update_release
create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation
]
@ -54,7 +54,7 @@ describe ProjectPolicy do
admin_snippet admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
daily_statistics read_deploy_token create_deploy_token destroy_deploy_token
read_deploy_token create_deploy_token destroy_deploy_token
admin_terraform_state
]
end

View File

@ -3,23 +3,23 @@
require 'spec_helper'
describe API::ProjectStatistics do
let(:maintainer) { create(:user) }
let(:public_project) { create(:project, :public) }
let_it_be(:developer) { create(:user) }
let_it_be(:public_project) { create(:project, :public) }
before do
public_project.add_maintainer(maintainer)
public_project.add_developer(developer)
end
describe 'GET /projects/:id/statistics' do
let!(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) }
let!(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) }
let!(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) }
let!(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) }
let!(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) }
let!(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) }
let_it_be(:fetch_statistics1) { create(:project_daily_statistic, project: public_project, fetch_count: 30, date: 29.days.ago) }
let_it_be(:fetch_statistics2) { create(:project_daily_statistic, project: public_project, fetch_count: 4, date: 3.days.ago) }
let_it_be(:fetch_statistics3) { create(:project_daily_statistic, project: public_project, fetch_count: 3, date: 2.days.ago) }
let_it_be(:fetch_statistics4) { create(:project_daily_statistic, project: public_project, fetch_count: 2, date: 1.day.ago) }
let_it_be(:fetch_statistics5) { create(:project_daily_statistic, project: public_project, fetch_count: 1, date: Date.today) }
let_it_be(:fetch_statistics_other_project) { create(:project_daily_statistic, project: create(:project), fetch_count: 29, date: 29.days.ago) }
it 'returns the fetch statistics of the last 30 days' do
get api("/projects/#{public_project.id}/statistics", maintainer)
get api("/projects/#{public_project.id}/statistics", developer)
expect(response).to have_gitlab_http_status(:ok)
fetches = json_response['fetches']
@ -32,7 +32,7 @@ describe API::ProjectStatistics do
it 'excludes the fetch statistics older than 30 days' do
create(:project_daily_statistic, fetch_count: 31, project: public_project, date: 30.days.ago)
get api("/projects/#{public_project.id}/statistics", maintainer)
get api("/projects/#{public_project.id}/statistics", developer)
expect(response).to have_gitlab_http_status(:ok)
fetches = json_response['fetches']
@ -41,11 +41,11 @@ describe API::ProjectStatistics do
expect(fetches['days'].last).to eq({ 'count' => fetch_statistics1.fetch_count, 'date' => fetch_statistics1.date.to_s })
end
it 'responds with 403 when the user is not a maintainer of the repository' do
developer = create(:user)
public_project.add_developer(developer)
it 'responds with 403 when the user is not a developer of the repository' do
guest = create(:user)
public_project.add_guest(guest)
get api("/projects/#{public_project.id}/statistics", developer)
get api("/projects/#{public_project.id}/statistics", guest)
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden')

View File

@ -10,8 +10,8 @@ require_relative '../../../../rubocop/cop/rspec/env_assignment'
describe RuboCop::Cop::RSpec::EnvAssignment do
include CopHelper
OFFENSE_CALL_SINGLE_QUOTES_KEY = %(ENV['FOO'] = 'bar').freeze
OFFENSE_CALL_DOUBLE_QUOTES_KEY = %(ENV["FOO"] = 'bar').freeze
offense_call_single_quotes_key = %(ENV['FOO'] = 'bar').freeze
offense_call_double_quotes_key = %(ENV["FOO"] = 'bar').freeze
let(:source_file) { 'spec/foo_spec.rb' }
@ -36,12 +36,12 @@ describe RuboCop::Cop::RSpec::EnvAssignment do
end
context 'with a key using single quotes' do
it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY
it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_SINGLE_QUOTES_KEY, %(stub_env('FOO', 'bar'))
it_behaves_like 'an offensive ENV#[]= call', offense_call_single_quotes_key
it_behaves_like 'an autocorrected ENV#[]= call', offense_call_single_quotes_key, %(stub_env('FOO', 'bar'))
end
context 'with a key using double quotes' do
it_behaves_like 'an offensive ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY
it_behaves_like 'an autocorrected ENV#[]= call', OFFENSE_CALL_DOUBLE_QUOTES_KEY, %(stub_env("FOO", 'bar'))
it_behaves_like 'an offensive ENV#[]= call', offense_call_double_quotes_key
it_behaves_like 'an autocorrected ENV#[]= call', offense_call_double_quotes_key, %(stub_env("FOO", 'bar'))
end
end

View File

@ -74,47 +74,6 @@ describe Snippets::CreateService do
end
end
shared_examples 'spam check is performed' do
shared_examples 'marked as spam' do
it 'marks a snippet as spam' do
expect(snippet).to be_spam
end
it 'invalidates the snippet' do
expect(snippet).to be_invalid
end
it 'creates a new spam_log' do
expect { snippet }
.to have_spam_log(title: snippet.title, noteable_type: snippet.class.name)
end
it 'assigns a spam_log to an issue' do
expect(snippet.spam_log).to eq(SpamLog.last)
end
end
let(:extra_opts) do
{ visibility_level: Gitlab::VisibilityLevel::PUBLIC, request: double(:request, env: {}) }
end
before do
expect_next_instance_of(Spam::AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: true)
end
end
[true, false, nil].each do |allow_possible_spam|
context "when recaptcha_disabled flag is #{allow_possible_spam.inspect}" do
before do
stub_feature_flags(allow_possible_spam: allow_possible_spam) unless allow_possible_spam.nil?
end
it_behaves_like 'marked as spam'
end
end
end
shared_examples 'snippet create data is tracked' do
let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
@ -280,7 +239,7 @@ describe Snippets::CreateService do
it_behaves_like 'a service that creates a snippet'
it_behaves_like 'public visibility level restrictions apply'
it_behaves_like 'spam check is performed'
it_behaves_like 'snippets spam check is performed'
it_behaves_like 'snippet create data is tracked'
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'
@ -306,7 +265,7 @@ describe Snippets::CreateService do
it_behaves_like 'a service that creates a snippet'
it_behaves_like 'public visibility level restrictions apply'
it_behaves_like 'spam check is performed'
it_behaves_like 'snippets spam check is performed'
it_behaves_like 'snippet create data is tracked'
it_behaves_like 'an error service response when save fails'
it_behaves_like 'creates repository and files'

View File

@ -7,7 +7,7 @@ describe Snippets::UpdateService do
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create :user, admin: true }
let(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
let(:options) do
let(:base_opts) do
{
title: 'Test snippet',
file_name: 'snippet.rb',
@ -15,6 +15,8 @@ describe Snippets::UpdateService do
visibility_level: visibility_level
}
end
let(:extra_opts) { {} }
let(:options) { base_opts.merge(extra_opts) }
let(:updater) { user }
let(:service) { Snippets::UpdateService.new(project, updater, options) }
@ -85,7 +87,7 @@ describe Snippets::UpdateService do
end
context 'when update fails' do
let(:options) { { title: '' } }
let(:extra_opts) { { title: '' } }
it 'does not increment count' do
expect { subject }.not_to change { counter.read(:update) }
@ -273,7 +275,7 @@ describe Snippets::UpdateService do
shared_examples 'committable attributes' do
context 'when file_name is updated' do
let(:options) { { file_name: 'snippet.rb' } }
let(:extra_opts) { { file_name: 'snippet.rb' } }
it 'commits to repository' do
expect(service).to receive(:create_commit)
@ -282,7 +284,7 @@ describe Snippets::UpdateService do
end
context 'when content is updated' do
let(:options) { { content: 'puts "hello world"' } }
let(:extra_opts) { { content: 'puts "hello world"' } }
it 'commits to repository' do
expect(service).to receive(:create_commit)
@ -314,6 +316,11 @@ describe Snippets::UpdateService do
it_behaves_like 'updates repository content'
it_behaves_like 'commit operation fails'
it_behaves_like 'committable attributes'
it_behaves_like 'snippets spam check is performed' do
before do
subject
end
end
context 'when snippet does not have a repository' do
let!(:snippet) { create(:project_snippet, author: user, project: project) }
@ -333,6 +340,11 @@ describe Snippets::UpdateService do
it_behaves_like 'updates repository content'
it_behaves_like 'commit operation fails'
it_behaves_like 'committable attributes'
it_behaves_like 'snippets spam check is performed' do
before do
subject
end
end
context 'when snippet does not have a repository' do
let!(:snippet) { create(:personal_snippet, author: user, project: project) }

View File

@ -39,7 +39,7 @@ RSpec.shared_context 'ProjectPolicy context' do
update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image
create_environment create_deployment update_deployment create_release update_release
update_environment
update_environment daily_statistics
]
end
@ -49,7 +49,6 @@ RSpec.shared_context 'ProjectPolicy context' do
admin_snippet admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
daily_statistics
]
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
RSpec.shared_examples 'snippets spam check is performed' do
shared_examples 'marked as spam' do
it 'marks a snippet as spam' do
expect(snippet).to be_spam
end
it 'invalidates the snippet' do
expect(snippet).to be_invalid
end
it 'creates a new spam_log' do
expect { snippet }
.to have_spam_log(title: snippet.title, noteable_type: snippet.class.name)
end
it 'assigns a spam_log to an issue' do
expect(snippet.spam_log).to eq(SpamLog.last)
end
end
let(:extra_opts) do
{ visibility_level: Gitlab::VisibilityLevel::PUBLIC, request: double(:request, env: {}) }
end
before do
expect_next_instance_of(Spam::AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: true)
end
end
[true, false, nil].each do |allow_possible_spam|
context "when allow_possible_spam flag is #{allow_possible_spam.inspect}" do
before do
stub_feature_flags(allow_possible_spam: allow_possible_spam) unless allow_possible_spam.nil?
end
it_behaves_like 'marked as spam'
end
end
end