diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-17 15:08:15 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-17 15:08:15 +0000 |
commit | c2b98d3dbd47ab92c79c702276fe9130d9a28036 (patch) | |
tree | bf4071f551fdc12c22b23b2bb66483064e7b9ea9 /spec | |
parent | badb9c1deacbea601b02f88811b7e123589d9251 (diff) | |
download | gitlab-ce-c2b98d3dbd47ab92c79c702276fe9130d9a28036.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
23 files changed, 703 insertions, 36 deletions
diff --git a/spec/controllers/projects/service_hook_logs_controller_spec.rb b/spec/controllers/projects/service_hook_logs_controller_spec.rb new file mode 100644 index 00000000000..ca57b0579a8 --- /dev/null +++ b/spec/controllers/projects/service_hook_logs_controller_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Projects::ServiceHookLogsController do + let(:project) { create(:project, :repository) } + let(:user) { create(:user) } + let(:service) { create(:drone_ci_service, project: project) } + let(:log) { create(:web_hook_log, web_hook: service.service_hook) } + let(:log_params) do + { + namespace_id: project.namespace, + project_id: project, + service_id: service.to_param, + id: log.id + } + end + + before do + sign_in(user) + project.add_maintainer(user) + end + + describe 'GET #show' do + subject { get :show, params: log_params } + + it do + expect(response).to be_successful + end + end + + describe 'POST #retry' do + subject { post :retry, params: log_params } + + it 'executes the hook and redirects to the service form' do + expect_any_instance_of(ServiceHook).to receive(:execute) + expect_any_instance_of(described_class).to receive(:set_hook_execution_notice) + expect(subject).to redirect_to(edit_project_service_path(project, service)) + end + end +end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index f9c77dbf87f..b6bb30d1f93 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -44,6 +44,13 @@ FactoryBot.define do end end + factory :drone_ci_service do + project + active { true } + drone_url { 'https://bamboo.example.com' } + token { 'test' } + end + factory :jira_service do project active { true } diff --git a/spec/frontend/monitoring/__snapshots__/dashboard_state_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap index 5f24bab600c..5f24bab600c 100644 --- a/spec/frontend/monitoring/__snapshots__/dashboard_state_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/empty_state_spec.js.snap diff --git a/spec/frontend/monitoring/components/__snapshots__/group_empty_state_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/group_empty_state_spec.js.snap new file mode 100644 index 00000000000..7f37a83d291 --- /dev/null +++ b/spec/frontend/monitoring/components/__snapshots__/group_empty_state_spec.js.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GroupEmptyState Renders an empty state for BAD_QUERY 1`] = ` +<glemptystate-stub + compact="true" + primarybuttonlink="/path/to/settings" + primarybuttontext="Verify configuration" + svgpath="/path/to/empty-group-illustration.svg" + title="Query cannot be processed" +/> +`; + +exports[`GroupEmptyState Renders an empty state for BAD_QUERY 2`] = `"The Prometheus server responded with \\"bad request\\". Please check your queries are correct and are supported in your Prometheus version. <a href=\\"/path/to/docs\\">More information</a>"`; + +exports[`GroupEmptyState Renders an empty state for CONNECTION_FAILED 1`] = ` +<glemptystate-stub + compact="true" + description="We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating." + primarybuttonlink="/path/to/settings" + primarybuttontext="Verify configuration" + svgpath="/path/to/empty-group-illustration.svg" + title="Connection failed" +/> +`; + +exports[`GroupEmptyState Renders an empty state for CONNECTION_FAILED 2`] = `undefined`; + +exports[`GroupEmptyState Renders an empty state for FOO STATE 1`] = ` +<glemptystate-stub + compact="true" + description="An error occurred while loading the data. Please try again." + svgpath="/path/to/empty-group-illustration.svg" + title="An error has occurred" +/> +`; + +exports[`GroupEmptyState Renders an empty state for FOO STATE 2`] = `undefined`; + +exports[`GroupEmptyState Renders an empty state for LOADING 1`] = ` +<glemptystate-stub + compact="true" + description="Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available." + svgpath="/path/to/empty-group-illustration.svg" + title="Waiting for performance data" +/> +`; + +exports[`GroupEmptyState Renders an empty state for LOADING 2`] = `undefined`; + +exports[`GroupEmptyState Renders an empty state for NO_DATA 1`] = ` +<glemptystate-stub + compact="true" + svgpath="/path/to/empty-group-illustration.svg" + title="No data to display" +/> +`; + +exports[`GroupEmptyState Renders an empty state for NO_DATA 2`] = `"The data source is connected, but there is no data to display. <a href=\\"/path/to/docs\\">More information</a>"`; + +exports[`GroupEmptyState Renders an empty state for TIMEOUT 1`] = ` +<glemptystate-stub + compact="true" + svgpath="/path/to/empty-group-illustration.svg" + title="Connection timed out" +/> +`; + +exports[`GroupEmptyState Renders an empty state for TIMEOUT 2`] = `"Charts can't be displayed as the request for data has timed out. <a href=\\"/path/to/docs\\">More information</a>"`; + +exports[`GroupEmptyState Renders an empty state for UNKNOWN_ERROR 1`] = ` +<glemptystate-stub + compact="true" + description="An error occurred while loading the data. Please try again." + svgpath="/path/to/empty-group-illustration.svg" + title="An error has occurred" +/> +`; + +exports[`GroupEmptyState Renders an empty state for UNKNOWN_ERROR 2`] = `undefined`; diff --git a/spec/frontend/monitoring/dashboard_state_spec.js b/spec/frontend/monitoring/components/empty_state_spec.js index e985e5fb443..e985e5fb443 100644 --- a/spec/frontend/monitoring/dashboard_state_spec.js +++ b/spec/frontend/monitoring/components/empty_state_spec.js diff --git a/spec/frontend/monitoring/components/group_empty_state_spec.js b/spec/frontend/monitoring/components/group_empty_state_spec.js new file mode 100644 index 00000000000..e8ef8192067 --- /dev/null +++ b/spec/frontend/monitoring/components/group_empty_state_spec.js @@ -0,0 +1,34 @@ +import { shallowMount } from '@vue/test-utils'; +import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; +import { metricStates } from '~/monitoring/constants'; + +function createComponent(props) { + return shallowMount(GroupEmptyState, { + propsData: { + ...props, + documentationPath: '/path/to/docs', + settingsPath: '/path/to/settings', + svgPath: '/path/to/empty-group-illustration.svg', + }, + }); +} + +describe('GroupEmptyState', () => { + const supportedStates = [ + metricStates.NO_DATA, + metricStates.TIMEOUT, + metricStates.CONNECTION_FAILED, + metricStates.BAD_QUERY, + metricStates.LOADING, + metricStates.UNKNOWN_ERROR, + 'FOO STATE', // does not fail with unknown states + ]; + + test.each(supportedStates)('Renders an empty state for %s', selectedState => { + const wrapper = createComponent({ selectedState }); + + expect(wrapper.element).toMatchSnapshot(); + // slot is not rendered by the stub, test it separately + expect(wrapper.vm.currentState.slottedDescription).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index 92d469270c9..f38bd4384e2 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -529,7 +529,7 @@ describe('Monitoring store actions', () => { }, }, { - type: types.RECEIVE_METRIC_RESULT_ERROR, + type: types.RECEIVE_METRIC_RESULT_FAILURE, payload: { metricId: metric.metric_id, error, diff --git a/spec/frontend/monitoring/store/getters_spec.js b/spec/frontend/monitoring/store/getters_spec.js index 3b6f33ed8b1..9e325fe3cf9 100644 --- a/spec/frontend/monitoring/store/getters_spec.js +++ b/spec/frontend/monitoring/store/getters_spec.js @@ -1,7 +1,7 @@ import * as getters from '~/monitoring/stores/getters'; - import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; +import { metricStates } from '~/monitoring/constants'; import { metricsGroupsAPIResponse, mockedEmptyResult, @@ -10,6 +10,124 @@ import { } from '../mock_data'; describe('Monitoring store Getters', () => { + describe('getMetricStates', () => { + let setupState; + let state; + let getMetricStates; + + beforeEach(() => { + setupState = (initState = {}) => { + state = initState; + getMetricStates = getters.getMetricStates(state); + }; + }); + + it('has method-style access', () => { + setupState(); + + expect(getMetricStates).toEqual(expect.any(Function)); + }); + + it('when dashboard has no panel groups, returns empty', () => { + setupState({ + dashboard: { + panel_groups: [], + }, + }); + + expect(getMetricStates()).toEqual([]); + }); + + describe('when the dashboard is set', () => { + let groups; + beforeEach(() => { + setupState({ + dashboard: { panel_groups: [] }, + }); + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + groups = state.dashboard.panel_groups; + }); + + it('no loaded metric returns empty', () => { + expect(getMetricStates()).toEqual([]); + }); + + it('on an empty metric with no result, returns NO_DATA', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult); + + expect(getMetricStates()).toEqual([metricStates.NO_DATA]); + }); + + it('on a metric with a result, returns OK', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); + + expect(getMetricStates()).toEqual([metricStates.OK]); + }); + + it('on a metric with an error, returns an error', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { + metricId: groups[0].panels[0].metrics[0].metricId, + }); + + expect(getMetricStates()).toEqual([metricStates.UNKNOWN_ERROR]); + }); + + it('on multiple metrics with results, returns OK', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); + mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal); + + expect(getMetricStates()).toEqual([metricStates.OK]); + + // Filtered by groups + expect(getMetricStates(state.dashboard.panel_groups[0].key)).toEqual([]); + expect(getMetricStates(state.dashboard.panel_groups[1].key)).toEqual([metricStates.OK]); + }); + it('on multiple metrics errors', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { + metricId: groups[0].panels[0].metrics[0].metricId, + }); + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { + metricId: groups[1].panels[0].metrics[0].metricId, + }); + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { + metricId: groups[1].panels[1].metrics[0].metricId, + }); + + // Entire dashboard fails + expect(getMetricStates()).toEqual([metricStates.UNKNOWN_ERROR]); + expect(getMetricStates(groups[0].key)).toEqual([metricStates.UNKNOWN_ERROR]); + expect(getMetricStates(groups[1].key)).toEqual([metricStates.UNKNOWN_ERROR]); + }); + + it('on multiple metrics with errors', () => { + mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); + + // An success in 1 group + mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); + // An error in 2 groups + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { + metricId: groups[0].panels[0].metrics[0].metricId, + }); + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { + metricId: groups[1].panels[1].metrics[0].metricId, + }); + + expect(getMetricStates()).toEqual([metricStates.OK, metricStates.UNKNOWN_ERROR]); + expect(getMetricStates(groups[0].key)).toEqual([metricStates.UNKNOWN_ERROR]); + expect(getMetricStates(groups[1].key)).toEqual([ + metricStates.OK, + metricStates.UNKNOWN_ERROR, + ]); + }); + }); + }); + describe('metricsWithData', () => { let metricsWithData; let setupState; diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js index 8da172ec634..60107a03674 100644 --- a/spec/frontend/monitoring/store/mutations_spec.js +++ b/spec/frontend/monitoring/store/mutations_spec.js @@ -3,7 +3,7 @@ import httpStatusCodes from '~/lib/utils/http_status'; import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import state from '~/monitoring/stores/state'; -import { metricsErrors } from '~/monitoring/constants'; +import { metricStates } from '~/monitoring/constants'; import { metricsGroupsAPIResponse, deploymentData, @@ -120,7 +120,7 @@ describe('Monitoring mutations', () => { expect.objectContaining({ loading: true, result: null, - error: null, + state: metricStates.LOADING, }), ); }); @@ -153,20 +153,20 @@ describe('Monitoring mutations', () => { expect(getMetric()).toEqual( expect.objectContaining({ loading: false, - error: null, + state: metricStates.OK, }), ); }); }); - describe('RECEIVE_METRIC_RESULT_ERROR', () => { + describe('RECEIVE_METRIC_RESULT_FAILURE', () => { beforeEach(() => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups); }); it('maintains the loading state when a metric fails', () => { expect(stateCopy.showEmptyState).toBe(true); - mutations[types.RECEIVE_METRIC_RESULT_ERROR](stateCopy, { + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, { metricId, error: 'an error', }); @@ -175,7 +175,7 @@ describe('Monitoring mutations', () => { }); it('stores a timeout error in a metric', () => { - mutations[types.RECEIVE_METRIC_RESULT_ERROR](stateCopy, { + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, { metricId, error: { message: 'BACKOFF_TIMEOUT' }, }); @@ -184,13 +184,13 @@ describe('Monitoring mutations', () => { expect.objectContaining({ loading: false, result: null, - error: metricsErrors.TIMEOUT, + state: metricStates.TIMEOUT, }), ); }); it('stores a connection failed error in a metric', () => { - mutations[types.RECEIVE_METRIC_RESULT_ERROR](stateCopy, { + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, { metricId, error: { response: { @@ -202,13 +202,13 @@ describe('Monitoring mutations', () => { expect.objectContaining({ loading: false, result: null, - error: metricsErrors.CONNECTION_FAILED, + state: metricStates.CONNECTION_FAILED, }), ); }); it('stores a bad data error in a metric', () => { - mutations[types.RECEIVE_METRIC_RESULT_ERROR](stateCopy, { + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, { metricId, error: { response: { @@ -221,13 +221,13 @@ describe('Monitoring mutations', () => { expect.objectContaining({ loading: false, result: null, - error: metricsErrors.BAD_DATA, + state: metricStates.BAD_QUERY, }), ); }); it('stores an unknown error in a metric', () => { - mutations[types.RECEIVE_METRIC_RESULT_ERROR](stateCopy, { + mutations[types.RECEIVE_METRIC_RESULT_FAILURE](stateCopy, { metricId, error: null, // no reason in response }); @@ -236,7 +236,7 @@ describe('Monitoring mutations', () => { expect.objectContaining({ loading: false, result: null, - error: metricsErrors.UNKNOWN_ERROR, + state: metricStates.UNKNOWN_ERROR, }), ); }); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 708c5ea75e0..0ee114cb70d 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -92,26 +92,58 @@ describe('Multi-file store actions', () => { .catch(done.fail); }); - it('closes the temp file if it was open', done => { + it('closes the temp file and deletes it if it was open', done => { f.tempFile = true; testAction( discardAllChanges, undefined, store.state, + [{ type: types.REMOVE_ALL_CHANGES_FILES }], [ - { type: types.DISCARD_FILE_CHANGES, payload: 'discardAll' }, - { type: types.REMOVE_ALL_CHANGES_FILES }, + { type: 'closeFile', payload: jasmine.objectContaining({ path: 'discardAll' }) }, + { type: 'deleteEntry', payload: 'discardAll' }, ], + done, + ); + }); + + it('renames the file to its original name and closes it if it was open', done => { + Object.assign(f, { + prevPath: 'parent/path/old_name', + prevName: 'old_name', + prevParentPath: 'parent/path', + }); + + testAction( + discardAllChanges, + undefined, + store.state, + [{ type: types.REMOVE_ALL_CHANGES_FILES }], [ + { type: 'closeFile', payload: jasmine.objectContaining({ path: 'discardAll' }) }, { - type: 'closeFile', - payload: jasmine.objectContaining({ path: 'discardAll' }), + type: 'renameEntry', + payload: { path: 'discardAll', name: 'old_name', parentPath: 'parent/path' }, }, ], done, ); }); + + it('discards file changes on all other files', done => { + testAction( + discardAllChanges, + undefined, + store.state, + [ + { type: types.DISCARD_FILE_CHANGES, payload: 'discardAll' }, + { type: types.REMOVE_ALL_CHANGES_FILES }, + ], + [], + done, + ); + }); }); describe('closeAllFiles', () => { diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js index 37a811f153f..b29bac21820 100644 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_spec.js @@ -4,7 +4,8 @@ import { GlToast } from '@gitlab/ui'; import VueDraggable from 'vuedraggable'; import MockAdapter from 'axios-mock-adapter'; import Dashboard from '~/monitoring/components/dashboard.vue'; -import EmptyState from '~/monitoring/components/empty_state.vue'; +import { metricStates } from '~/monitoring/constants'; +import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; import * as types from '~/monitoring/stores/mutation_types'; import { createStore } from '~/monitoring/stores'; import axios from '~/lib/utils/axios_utils'; @@ -401,7 +402,7 @@ describe('Dashboard', () => { }); beforeEach(done => { - createComponentWrapper({ hasMetrics: true }, { attachToDocument: true }); + createComponentWrapper({ hasMetrics: true }); setupComponentStore(wrapper.vm); wrapper.vm.$nextTick(done); @@ -411,16 +412,16 @@ describe('Dashboard', () => { const emptyGroup = wrapper.findAll({ ref: 'empty-group' }); expect(emptyGroup).toHaveLength(1); - expect(emptyGroup.is(EmptyState)).toBe(true); + expect(emptyGroup.is(GroupEmptyState)).toBe(true); }); - it('group empty area displays a "noDataGroup"', () => { + it('group empty area displays a NO_DATA state', () => { expect( wrapper .findAll({ ref: 'empty-group' }) .at(0) .props('selectedState'), - ).toEqual('noDataGroup'); + ).toEqual(metricStates.NO_DATA); }); }); diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb index 9e55fbcce20..2c141cae98d 100644 --- a/spec/models/blob_spec.rb +++ b/spec/models/blob_spec.rb @@ -421,4 +421,21 @@ describe Blob do end end end + + describe 'policy' do + let(:project) { build(:project) } + subject { described_class.new(fake_blob(path: 'foo'), project) } + + it 'works with policy' do + expect(Ability.allowed?(project.creator, :read_blob, subject)).to be_truthy + end + + context 'when project is nil' do + subject { described_class.new(fake_blob(path: 'foo')) } + + it 'does not err' do + expect(Ability.allowed?(project.creator, :read_blob, subject)).to be_falsey + end + end + end end diff --git a/spec/models/concerns/safe_url_spec.rb b/spec/models/concerns/safe_url_spec.rb new file mode 100644 index 00000000000..3244410181e --- /dev/null +++ b/spec/models/concerns/safe_url_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SafeUrl do + describe '#safe_url' do + class TestClass + include SafeUrl + + attr_reader :url + + def initialize(url) + @url = url + end + end + + let(:test_class) { TestClass.new(url) } + let(:url) { 'http://example.com' } + + subject { test_class.safe_url } + + it { is_expected.to eq(url) } + + context 'when URL contains credentials' do + let(:url) { 'http://foo:bar@example.com' } + + it { is_expected.to eq('http://*****:*****@example.com')} + + context 'when username is whitelisted' do + subject { test_class.safe_url(usernames_whitelist: usernames_whitelist) } + + let(:usernames_whitelist) { %w[foo] } + + it 'does expect the whitelisted username not to be masked' do + is_expected.to eq('http://foo:*****@example.com') + end + end + end + + context 'when URL is empty' do + let(:url) { nil } + + it { is_expected.to be_nil } + end + + context 'when URI raises an error' do + let(:url) { 123 } + + it { is_expected.to be_nil } + end + end +end diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index 85934b81086..22aad2fab0a 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -29,6 +29,25 @@ describe WebHookLog do end end + describe '#save' do + let(:web_hook_log) { build(:web_hook_log, url: url) } + let(:url) { 'http://example.com' } + + subject { web_hook_log.save! } + + it { is_expected.to eq(true) } + + context 'with basic auth credentials' do + let(:url) { 'http://test:123@example.com'} + + it 'obfuscates the basic auth credentials' do + subject + + expect(web_hook_log.url).to eq('http://*****:*****@example.com') + end + end + end + describe '#success?' do let(:web_hook_log) { build(:web_hook_log, response_status: status) } diff --git a/spec/models/readme_blob_spec.rb b/spec/models/readme_blob_spec.rb new file mode 100644 index 00000000000..f07713bd908 --- /dev/null +++ b/spec/models/readme_blob_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ReadmeBlob do + include FakeBlobHelpers + + describe 'policy' do + let(:project) { build(:project, :repository) } + subject { described_class.new(fake_blob(path: 'README.md'), project.repository) } + + it 'works with policy' do + expect(Ability.allowed?(project.creator, :read_blob, subject)).to be_truthy + end + end +end diff --git a/spec/policies/blob_policy_spec.rb b/spec/policies/blob_policy_spec.rb new file mode 100644 index 00000000000..20c8a55f437 --- /dev/null +++ b/spec/policies/blob_policy_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe BlobPolicy do + include_context 'ProjectPolicyTable context' + include ProjectHelpers + using RSpec::Parameterized::TableSyntax + + let(:project) { create(:project, :repository, project_level) } + let(:user) { create_user_from_membership(project, membership) } + let(:blob) { project.repository.blob_at(SeedRepo::FirstCommit::ID, 'README.md') } + + subject(:policy) { described_class.new(user, blob) } + + where(:project_level, :feature_access_level, :membership, :expected_count) do + permission_table_for_guest_feature_access_and_non_private_project_only + end + + with_them do + it "grants permission" do + update_feature_access_level(project, feature_access_level) + + if expected_count == 1 + expect(policy).to be_allowed(:read_blob) + else + expect(policy).to be_disallowed(:read_blob) + end + end + end +end diff --git a/spec/policies/wiki_page_policy_spec.rb b/spec/policies/wiki_page_policy_spec.rb new file mode 100644 index 00000000000..e550ccf6d65 --- /dev/null +++ b/spec/policies/wiki_page_policy_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe WikiPagePolicy do + include_context 'ProjectPolicyTable context' + include ProjectHelpers + using RSpec::Parameterized::TableSyntax + + let(:project) { create(:project, :wiki_repo, project_level) } + let(:user) { create_user_from_membership(project, membership) } + let(:wiki_page) { create(:wiki_page, wiki: project.wiki) } + + subject(:policy) { described_class.new(user, wiki_page) } + + where(:project_level, :feature_access_level, :membership, :expected_count) do + permission_table_for_guest_feature_access + end + + with_them do + it "grants permission" do + update_feature_access_level(project, feature_access_level) + + if expected_count == 1 + expect(policy).to be_allowed(:read_wiki_page) + else + expect(policy).to be_disallowed(:read_wiki_page) + end + end + end +end diff --git a/spec/presenters/hooks/project_hook_presenter_spec.rb b/spec/presenters/hooks/project_hook_presenter_spec.rb new file mode 100644 index 00000000000..773e8ccf51e --- /dev/null +++ b/spec/presenters/hooks/project_hook_presenter_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ProjectHookPresenter do + let(:web_hook_log) { create(:web_hook_log) } + let(:project) { web_hook_log.web_hook.project } + let(:web_hook) { web_hook_log.web_hook } + + describe '#logs_details_path' do + subject { web_hook.present.logs_details_path(web_hook_log) } + + let(:expected_path) do + "/#{project.namespace.path}/#{project.name}/hooks/#{web_hook.id}/hook_logs/#{web_hook_log.id}" + end + + it { is_expected.to eq(expected_path) } + end + + describe '#logs_retry_path' do + subject { web_hook.present.logs_details_path(web_hook_log) } + + let(:expected_path) do + "/#{project.namespace.path}/#{project.name}/hooks/#{web_hook.id}/hook_logs/#{web_hook_log.id}" + end + + it { is_expected.to eq(expected_path) } + end +end diff --git a/spec/presenters/hooks/service_hook_presenter_spec.rb b/spec/presenters/hooks/service_hook_presenter_spec.rb new file mode 100644 index 00000000000..bea57768e3e --- /dev/null +++ b/spec/presenters/hooks/service_hook_presenter_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ServiceHookPresenter do + let(:web_hook_log) { create(:web_hook_log, web_hook: service_hook) } + let(:service_hook) { create(:service_hook, service: service) } + let(:service) { create(:drone_ci_service, project: project) } + let(:project) { create(:project) } + + describe '#logs_details_path' do + subject { service_hook.present.logs_details_path(web_hook_log) } + + let(:expected_path) do + "/#{project.namespace.path}/#{project.name}/-/services/#{service.to_param}/hook_logs/#{web_hook_log.id}" + end + + it { is_expected.to eq(expected_path) } + end + + describe '#logs_retry_path' do + subject { service_hook.present.logs_retry_path(web_hook_log) } + + let(:expected_path) do + "/#{project.namespace.path}/#{project.name}/-/services/#{service.to_param}/hook_logs/#{web_hook_log.id}/retry" + end + + it { is_expected.to eq(expected_path) } + end +end diff --git a/spec/presenters/web_hook_log_presenter_spec.rb b/spec/presenters/web_hook_log_presenter_spec.rb new file mode 100644 index 00000000000..8812a0ba594 --- /dev/null +++ b/spec/presenters/web_hook_log_presenter_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe WebHookLogPresenter do + include Gitlab::Routing.url_helpers + + describe '#details_path' do + let(:web_hook_log) { create(:web_hook_log, web_hook: web_hook) } + let(:project) { create(:project) } + + subject { web_hook_log.present.details_path } + + context 'project hook' do + let(:web_hook) { create(:project_hook, project: project) } + + it { is_expected.to eq(project_hook_hook_log_path(project, web_hook, web_hook_log)) } + end + + context 'service hook' do + let(:web_hook) { create(:service_hook, service: service) } + let(:service) { create(:drone_ci_service, project: project) } + + it { is_expected.to eq(project_service_hook_log_path(project, service, web_hook_log)) } + end + end + + describe '#retry_path' do + let(:web_hook_log) { create(:web_hook_log, web_hook: web_hook) } + let(:project) { create(:project) } + + subject { web_hook_log.present.retry_path } + + context 'project hook' do + let(:web_hook) { create(:project_hook, project: project) } + + it { is_expected.to eq(retry_project_hook_hook_log_path(project, web_hook, web_hook_log)) } + end + + context 'service hook' do + let(:web_hook) { create(:service_hook, service: service) } + let(:service) { create(:drone_ci_service, project: project) } + + it { is_expected.to eq(retry_project_service_hook_log_path(project, service, web_hook_log)) } + end + end +end diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb new file mode 100644 index 00000000000..0e8fe4987b9 --- /dev/null +++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Mark snippet as spam' do + include GraphqlHelpers + + let_it_be(:admin) { create(:admin) } + let_it_be(:other_user) { create(:user) } + let_it_be(:snippet) { create(:personal_snippet) } + let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: snippet) } + let(:current_user) { snippet.author } + let(:mutation) do + variables = { + id: snippet.to_global_id.to_s + } + + graphql_mutation(:mark_as_spam_snippet, variables) + end + + def mutation_response + graphql_mutation_response(:mark_as_spam_snippet) + end + + shared_examples 'does not mark the snippet as spam' do + it do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.not_to change { snippet.reload.user_agent_detail.submitted } + end + end + + context 'when the user does not have permission' do + let(:current_user) { other_user } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + + it_behaves_like 'does not mark the snippet as spam' + end + + context 'when the user has permission' do + context 'when user can not mark snippet as spam' do + it_behaves_like 'does not mark the snippet as spam' + end + + context 'when user can mark snippet as spam' do + let(:current_user) { admin } + + before do + stub_application_setting(akismet_enabled: true) + end + + it 'marks snippet as spam' do + expect_next_instance_of(SpamService) do |instance| + expect(instance).to receive(:mark_as_spam!) + end + + post_graphql_mutation(mutation, current_user: current_user) + end + end + end +end diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index 2a4368868d5..d8f13bc2e61 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -203,17 +203,6 @@ describe WebHookService do expect(hook_log.internal_error_message).to be_nil end end - - context 'should not log ServiceHooks' do - let(:service_hook) { create(:service_hook) } - let(:service_instance) { described_class.new(service_hook, data, 'service_hook') } - - before do - stub_full_request(service_hook.url, method: :post).to_return(status: 200, body: 'Success') - end - - it { expect { service_instance.execute }.not_to change(WebHookLog, :count) } - end end end diff --git a/spec/views/projects/services/edit.html.haml_spec.rb b/spec/views/projects/services/edit.html.haml_spec.rb new file mode 100644 index 00000000000..12e1cda2c00 --- /dev/null +++ b/spec/views/projects/services/edit.html.haml_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'projects/services/edit' do + let(:service) { create(:drone_ci_service, project: project) } + let(:project) { create(:project) } + + before do + assign :project, project + assign :service, service + end + + it do + render + + expect(rendered).not_to have_text('Recent Deliveries') + end + + context 'service using WebHooks' do + before do + assign(:web_hook_logs, []) + end + + it do + render + + expect(rendered).to have_text('Recent Deliveries') + end + end +end |