diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-05 18:10:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-05 18:10:10 +0300 |
commit | c4acd4624d61ef923453561a698c905c0ffa7b57 (patch) | |
tree | 8248d88e7c9957e20f1aaa95d814d13f4d1e65ce /spec | |
parent | 1c359370b3970c41dd8aa0c69fe177f39cb58298 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
34 files changed, 563 insertions, 848 deletions
diff --git a/spec/controllers/projects/grafana_api_controller_spec.rb b/spec/controllers/projects/grafana_api_controller_spec.rb deleted file mode 100644 index 9bc4a83030e..00000000000 --- a/spec/controllers/projects/grafana_api_controller_spec.rb +++ /dev/null @@ -1,268 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Projects::GrafanaApiController, feature_category: :metrics do - let_it_be(:project) { create(:project, :public) } - let_it_be(:reporter) { create(:user) } - let_it_be(:guest) { create(:user) } - let(:anonymous) { nil } - let(:user) { reporter } - - before_all do - project.add_reporter(reporter) - project.add_guest(guest) - end - - before do - stub_feature_flags(remove_monitor_metrics: false) - sign_in(user) if user - end - - describe 'GET #proxy' do - let(:proxy_service) { instance_double(Grafana::ProxyService) } - let(:params) do - { - namespace_id: project.namespace.full_path, - project_id: project.path, - proxy_path: 'api/v1/query_range', - datasource_id: '1', - query: 'rate(relevant_metric)', - start_time: '1570441248', - end_time: '1570444848', - step: '900' - } - end - - before do - allow(Grafana::ProxyService).to receive(:new).and_return(proxy_service) - allow(proxy_service).to receive(:execute).and_return(service_result) - end - - shared_examples_for 'error response' do |http_status| - it "returns #{http_status}" do - get :proxy, params: params - - expect(response).to have_gitlab_http_status(http_status) - expect(json_response['status']).to eq('error') - expect(json_response['message']).to eq('error message') - end - end - - shared_examples_for 'accessible' do - let(:service_result) { nil } - - it 'returns non erroneous response' do - get :proxy, params: params - - # We don't care about the specific code as long it's not an error. - expect(response).to have_gitlab_http_status(:no_content) - end - end - - shared_examples_for 'not accessible' do - let(:service_result) { nil } - - it 'returns 404 Not found' do - get :proxy, params: params - - expect(response).to have_gitlab_http_status(:not_found) - expect(Grafana::ProxyService).not_to have_received(:new) - end - end - - shared_examples_for 'login required' do - let(:service_result) { nil } - - it 'redirects to login page' do - get :proxy, params: params - - expect(response).to redirect_to(new_user_session_path) - expect(Grafana::ProxyService).not_to have_received(:new) - end - end - - context 'with a successful result' do - let(:service_result) { { status: :success, body: '{}' } } - - it 'returns a grafana datasource response' do - get :proxy, params: params - - expect(Grafana::ProxyService).to have_received(:new).with( - project, '1', 'api/v1/query_range', - { - 'query' => params[:query], - 'start' => params[:start_time], - 'end' => params[:end_time], - 'step' => params[:step] - } - ) - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to eq({}) - end - end - - context 'when the request is still unavailable' do - let(:service_result) { nil } - - it 'returns 204 no content' do - get :proxy, params: params - - expect(response).to have_gitlab_http_status(:no_content) - expect(json_response['status']).to eq('processing') - expect(json_response['message']).to eq('Not ready yet. Try again later.') - end - end - - context 'when an error has occurred' do - context 'with an error accessing grafana' do - let(:service_result) do - { - http_status: :service_unavailable, - status: :error, - message: 'error message' - } - end - - it_behaves_like 'error response', :service_unavailable - end - - context 'with a processing error' do - let(:service_result) do - { - status: :error, - message: 'error message' - } - end - - it_behaves_like 'error response', :bad_request - end - end - - context 'as guest' do - let(:user) { guest } - - it_behaves_like 'not accessible' - end - - context 'as anonymous' do - let(:user) { anonymous } - - it_behaves_like 'not accessible' - end - - context 'on a private project' do - let_it_be(:project) { create(:project, :private) } - - before_all do - project.add_guest(guest) - end - - context 'as anonymous' do - let(:user) { anonymous } - - it_behaves_like 'login required' - end - - context 'as guest' do - let(:user) { guest } - - it_behaves_like 'accessible' - end - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it_behaves_like 'not accessible' - end - end - - describe 'GET #metrics_dashboard' do - let(:service_result) { { status: :success, dashboard: '{}' } } - let(:params) do - { - format: :json, - embedded: true, - grafana_url: 'https://grafana.example.com', - namespace_id: project.namespace.full_path, - project_id: project.path - } - end - - before do - allow(Gitlab::Metrics::Dashboard::Finder) - .to receive(:find) - .and_return(service_result) - end - - context 'when the result is still processing' do - let(:service_result) { nil } - - it 'returns 204 no content' do - get :metrics_dashboard, params: params - - expect(response).to have_gitlab_http_status(:no_content) - end - end - - context 'when the result was successful' do - it 'returns the dashboard response' do - get :metrics_dashboard, params: params - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to include({ - 'dashboard' => '{}', - 'status' => 'success' - }) - expect(json_response).to include('metrics_data') - end - end - - context 'when an error has occurred' do - shared_examples_for 'error response' do |http_status| - it "returns #{http_status}" do - get :metrics_dashboard, params: params - - expect(response).to have_gitlab_http_status(http_status) - expect(json_response['status']).to eq('error') - expect(json_response['message']).to eq('error message') - end - end - - context 'with an error accessing grafana' do - let(:service_result) do - { - http_status: :service_unavailable, - status: :error, - message: 'error message' - } - end - - it_behaves_like 'error response', :service_unavailable - end - - context 'with a processing error' do - let(:service_result) { { status: :error, message: 'error message' } } - - it_behaves_like 'error response', :bad_request - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns 404 Not found' do - get :metrics_dashboard, params: params - - expect(response).to have_gitlab_http_status(:not_found) - expect(response.body).to be_empty - end - end - end - end -end diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb index 82b7379b67c..ae3859280b1 100644 --- a/spec/features/abuse_report_spec.rb +++ b/spec/features/abuse_report_spec.rb @@ -13,6 +13,7 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do before do sign_in(reporter1) stub_feature_flags(moved_mr_sidebar: false) + stub_feature_flags(user_profile_overflow_menu_vue: false) end describe 'report abuse to administrator' do @@ -122,6 +123,10 @@ RSpec.describe 'Abuse reports', :js, feature_category: :insider_threat do end end + # TODO: implement tests before the FF "user_profile_overflow_menu_vue" is turned on + # See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971 + # Related Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/416983 + private def fill_and_submit_abuse_category_form(category = "They're posting spam.") diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb index bc37c9941ce..39b6d049e43 100644 --- a/spec/features/users/rss_spec.rb +++ b/spec/features/users/rss_spec.rb @@ -6,6 +6,10 @@ RSpec.describe 'User RSS', feature_category: :user_profile do let(:user) { create(:user) } let(:path) { user_path(create(:user)) } + before do + stub_feature_flags(user_profile_overflow_menu_vue: false) + end + context 'when signed in' do before do sign_in(user) @@ -22,4 +26,8 @@ RSpec.describe 'User RSS', feature_category: :user_profile do it_behaves_like "it has an RSS button without a feed token" end + + # TODO: implement tests before the FF "user_profile_overflow_menu_vue" is turned on + # See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122971 + # Related Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/416974 end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index 9c4a1b36ecc..f8653b22377 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -9,10 +9,32 @@ RSpec.describe 'User page', feature_category: :user_profile do subject(:visit_profile) { visit(user_path(user)) } - it 'shows user id' do - subject + context 'with "user_profile_overflow_menu_vue" feature flag enabled', :js do + it 'does not show the user id in the profile info' do + subject + + expect(page).not_to have_content("User ID: #{user.id}") + end + + it 'shows copy user id action in the dropdown' do + subject + + find('[data-testid="base-dropdown-toggle"').click + + expect(page).to have_content("Copy user ID: #{user.id}") + end + end + + context 'with "user_profile_overflow_menu_vue" feature flag disabled', :js do + before do + stub_feature_flags(user_profile_overflow_menu_vue: false) + end + + it 'shows user id' do + subject - expect(page).to have_content("User ID: #{user.id}") + expect(page).to have_content("User ID: #{user.id}") + end end it 'shows name on breadcrumbs' do diff --git a/spec/frontend/lib/utils/forms_spec.js b/spec/frontend/lib/utils/forms_spec.js index 2f71b26b29a..b97f5bf3c51 100644 --- a/spec/frontend/lib/utils/forms_spec.js +++ b/spec/frontend/lib/utils/forms_spec.js @@ -1,7 +1,12 @@ import { serializeForm, serializeFormObject, + safeTrim, isEmptyValue, + hasMinimumLength, + isParseableAsInteger, + isIntegerGreaterThan, + isEmail, parseRailsFormFields, } from '~/lib/utils/forms'; @@ -99,6 +104,22 @@ describe('lib/utils/forms', () => { }); }); + describe('safeTrim', () => { + it.each` + input | returnValue + ${''} | ${''} + ${[]} | ${[]} + ${null} | ${null} + ${undefined} | ${undefined} + ${' '} | ${''} + ${'hello '} | ${'hello'} + ${'hello'} | ${'hello'} + ${0} | ${0} + `('returns $returnValue for value $input', ({ input, returnValue }) => { + expect(safeTrim(input)).toEqual(returnValue); + }); + }); + describe('isEmptyValue', () => { it.each` input | returnValue @@ -106,14 +127,102 @@ describe('lib/utils/forms', () => { ${[]} | ${true} ${null} | ${true} ${undefined} | ${true} + ${' '} | ${true} ${'hello'} | ${false} - ${' '} | ${false} ${0} | ${false} `('returns $returnValue for value $input', ({ input, returnValue }) => { expect(isEmptyValue(input)).toBe(returnValue); }); }); + describe('hasMinimumLength', () => { + it.each` + input | minLength | returnValue + ${['o', 't']} | ${1} | ${true} + ${'hello'} | ${3} | ${true} + ${' '} | ${2} | ${false} + ${''} | ${0} | ${false} + ${''} | ${8} | ${false} + ${[]} | ${0} | ${false} + ${null} | ${8} | ${false} + ${undefined} | ${8} | ${false} + ${'hello'} | ${8} | ${false} + ${0} | ${8} | ${false} + ${4} | ${1} | ${false} + `( + 'returns $returnValue for value $input and minLength $minLength', + ({ input, minLength, returnValue }) => { + expect(hasMinimumLength(input, minLength)).toBe(returnValue); + }, + ); + }); + + describe('isPareseableInteger', () => { + it.each` + input | returnValue + ${'0'} | ${true} + ${'12'} | ${true} + ${''} | ${false} + ${[]} | ${false} + ${null} | ${false} + ${undefined} | ${false} + ${'hello'} | ${false} + ${' '} | ${false} + ${'12.4'} | ${false} + ${'12ef'} | ${false} + `('returns $returnValue for value $input', ({ input, returnValue }) => { + expect(isParseableAsInteger(input)).toBe(returnValue); + }); + }); + + describe('isIntegerGreaterThan', () => { + it.each` + input | greaterThan | returnValue + ${25} | ${8} | ${true} + ${'25'} | ${8} | ${true} + ${'4'} | ${1} | ${true} + ${'4'} | ${8} | ${false} + ${'9.5'} | ${8} | ${false} + ${'9.5e'} | ${8} | ${false} + ${['o', 't']} | ${0} | ${false} + ${'hello'} | ${0} | ${false} + ${' '} | ${0} | ${false} + ${''} | ${0} | ${false} + ${''} | ${8} | ${false} + ${[]} | ${0} | ${false} + ${null} | ${0} | ${false} + ${undefined} | ${0} | ${false} + ${'hello'} | ${0} | ${false} + ${0} | ${0} | ${false} + `( + 'returns $returnValue for value $input and greaterThan $greaterThan', + ({ input, greaterThan, returnValue }) => { + expect(isIntegerGreaterThan(input, greaterThan)).toBe(returnValue); + }, + ); + }); + + describe('isEmail', () => { + it.each` + input | returnValue + ${'user-with_special-chars@example.com'} | ${true} + ${'user@subdomain.example.com'} | ${true} + ${'user@example.com'} | ${true} + ${'user@example.co'} | ${true} + ${'user@example.c'} | ${false} + ${'user@example'} | ${false} + ${''} | ${false} + ${[]} | ${false} + ${null} | ${false} + ${undefined} | ${false} + ${'hello'} | ${false} + ${' '} | ${false} + ${'12'} | ${false} + `('returns $returnValue for value $input', ({ input, returnValue }) => { + expect(isEmail(input)).toBe(returnValue); + }); + }); + describe('serializeFormObject', () => { it('returns an serialized object', () => { const form = { diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js index 9714093e001..85eb7e2e241 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js @@ -10,6 +10,7 @@ import { USER_HANDLE, PATH_HANDLE, SEARCH_SCOPE, + MAX_ROWS, } from '~/super_sidebar/components/global_search/command_palette/constants'; import { commandMapper, @@ -164,7 +165,26 @@ describe('CommandPaletteItems', () => { expect(findLoader().exists()).toBe(true); }); - it('should render returned items', async () => { + it(`should render all items when returned number of items is less than ${MAX_ROWS}`, async () => { + const numberOfItems = MAX_ROWS - 1; + const items = FILES.slice(0, numberOfItems).map(fileMapper.bind(null, projectBlobPath)); + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, FILES.slice(0, numberOfItems)); + jest.spyOn(fuzzaldrinPlus, 'filter').mockReturnValue(items); + + const searchQuery = 'gitlab-ci.yml'; + createComponent({ handle: PATH_HANDLE, searchQuery }); + + await waitForPromises(); + + expect(findGroups().at(0).props('group')).toMatchObject({ + name: PATH_GROUP_TITLE, + items: items.slice(0, MAX_ROWS), + }); + + expect(findItems()).toHaveLength(numberOfItems); + }); + + it(`should render first ${MAX_ROWS} returned items when number of returned items exceeds ${MAX_ROWS}`, async () => { const items = FILES.map(fileMapper.bind(null, projectBlobPath)); mockAxios.onGet().replyOnce(HTTP_STATUS_OK, FILES); jest.spyOn(fuzzaldrinPlus, 'filter').mockReturnValue(items); @@ -174,10 +194,10 @@ describe('CommandPaletteItems', () => { await waitForPromises(); - expect(findItems()).toHaveLength(items.length); + expect(findItems()).toHaveLength(MAX_ROWS); expect(findGroups().at(0).props('group')).toMatchObject({ name: PATH_GROUP_TITLE, - items, + items: items.slice(0, MAX_ROWS), }); }); diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js b/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js index 26a6501c338..d01e5c85741 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js @@ -133,13 +133,44 @@ export const ISSUE = { }; export const FILES = [ - '.codeclimate.yml', + '.gitattributes', '.gitignore', - '.gitlab-ci.yml', - '.gitlab/CODEOWNERS', - '.ruby-version', - '.tool-versions', + '.gitmodules', 'CHANGELOG', 'CONTRIBUTING.md', - 'Dangerfile', + 'Gemfile.zip', + 'LICENSE', + 'MAINTENANCE.md', + 'PROCESS.md', + 'README', + 'README.md', + 'VERSION', + 'bar/branch-test.txt', + 'custom-highlighting/test.gitlab-custom', + 'encoding/feature-1.txt', + 'encoding/feature-2.txt', + 'encoding/hotfix-1.txt', + 'encoding/hotfix-2.txt', + 'encoding/iso8859.txt', + 'encoding/russian.rb', + 'encoding/test.txt', + 'encoding/テスト.txt', + 'encoding/テスト.xls', + 'files/flat/path/correct/content.txt', + 'files/html/500.html', + 'files/images/6049019_460s.jpg', + 'files/images/emoji.png', + 'files/images/logo-black.png', + 'files/images/logo-white.png', + 'files/images/wm.svg', + 'files/js/application.js', + 'files/js/commit.coffee', + 'files/lfs/lfs_object.iso', + 'files/markdown/ruby-style-guide.md', + 'files/ruby/popen.rb', + 'files/ruby/regex.rb', + 'files/ruby/version_info.rb', + 'files/whitespace', + 'foo/bar/.gitkeep', + 'with space/README.md', ]; diff --git a/spec/frontend/users/profile/actions/components/user_actions_app_spec.js b/spec/frontend/users/profile/actions/components/user_actions_app_spec.js new file mode 100644 index 00000000000..d27962440ee --- /dev/null +++ b/spec/frontend/users/profile/actions/components/user_actions_app_spec.js @@ -0,0 +1,38 @@ +import { GlDisclosureDropdown } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import UserActionsApp from '~/users/profile/actions/components/user_actions_app.vue'; + +describe('User Actions App', () => { + let wrapper; + + const USER_ID = 'test-id'; + + const createWrapper = (propsData = {}) => { + wrapper = mountExtended(UserActionsApp, { + propsData: { + userId: USER_ID, + ...propsData, + }, + }); + }; + + const findDropdown = () => wrapper.findComponent(GlDisclosureDropdown); + const findActions = () => wrapper.findAllByTestId('disclosure-dropdown-item'); + const findAction = (position = 0) => findActions().at(position); + + it('shows dropdown', () => { + createWrapper(); + expect(findDropdown().exists()).toBe(true); + }); + + it('shows actions correctly', () => { + createWrapper(); + expect(findActions()).toHaveLength(1); + }); + + it('shows copy user id action', () => { + createWrapper(); + expect(findAction().text()).toBe(`Copy user ID: ${USER_ID}`); + expect(findAction().findComponent('button').attributes('data-clipboard-text')).toBe(USER_ID); + }); +}); diff --git a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb b/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb deleted file mode 100644 index 354fd350aa7..00000000000 --- a/spec/graphql/resolvers/metrics/dashboard_resolver_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Resolvers::Metrics::DashboardResolver, feature_category: :metrics do - include GraphqlHelpers - - let_it_be(:current_user) { create(:user) } - - describe '#resolve' do - subject(:resolve_dashboard) { resolve(described_class, obj: parent_object, args: args, ctx: { current_user: current_user }) } - - let(:args) do - { - path: 'config/prometheus/common_metrics.yml' - } - end - - context 'for environment' do - let(:project) { create(:project) } - let(:parent_object) { create(:environment, project: project) } - - before do - stub_feature_flags(remove_monitor_metrics: false) - project.add_developer(current_user) - end - - it 'use ActiveModel class to find matching dashboard', :aggregate_failures do - expected_arguments = { project: project, user: current_user, path: args[:path], options: { environment: parent_object } } - - expect(PerformanceMonitoring::PrometheusDashboard).to receive(:find_for).with(expected_arguments).and_return(PerformanceMonitoring::PrometheusDashboard.new) - expect(resolve_dashboard).to be_instance_of PerformanceMonitoring::PrometheusDashboard - end - - context 'without parent object' do - let(:parent_object) { nil } - - it 'returns nil', :aggregate_failures do - expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:find_for) - expect(resolve_dashboard).to be_nil - end - end - - context 'when metrics dashboard feature is unavailable' do - before do - stub_feature_flags(remove_monitor_metrics: true) - end - - it 'returns nil', :aggregate_failures do - expect(PerformanceMonitoring::PrometheusDashboard).not_to receive(:find_for) - expect(resolve_dashboard).to be_nil - end - end - end - end -end diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb index 721c20efc81..1d1bc4b2cb4 100644 --- a/spec/graphql/types/environment_type_spec.rb +++ b/spec/graphql/types/environment_type_spec.rb @@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['Environment'] do it 'includes the expected fields' do expected_fields = %w[ - name id state metrics_dashboard latest_opened_most_severe_alert path external_url deployments + name id state latest_opened_most_severe_alert path external_url deployments slug createdAt updatedAt autoStopAt autoDeleteAt tier environmentType lastDeployment deployFreezes clusterAgent ] diff --git a/spec/graphql/types/metrics/dashboard_type_spec.rb b/spec/graphql/types/metrics/dashboard_type_spec.rb deleted file mode 100644 index 114db87d5f1..00000000000 --- a/spec/graphql/types/metrics/dashboard_type_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe GitlabSchema.types['MetricsDashboard'] do - specify { expect(described_class.graphql_name).to eq('MetricsDashboard') } - - it 'has the expected fields' do - expected_fields = %w[ - path annotations schema_validation_warnings - ] - - expect(described_class).to have_graphql_fields(*expected_fields) - end - - describe 'annotations field' do - subject { described_class.fields['annotations'] } - - it { is_expected.to have_graphql_type(Types::Metrics::Dashboards::AnnotationType.connection_type) } - it { is_expected.to have_graphql_resolver(Resolvers::Metrics::Dashboards::AnnotationResolver) } - end -end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index ffaffa251d1..a2b8ee061bb 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -666,9 +666,11 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do describe '#sidebar_milestone_tooltip_label' do it 'escapes HTML in the milestone title' do - milestone = build(:milestone, title: '<img onerror=alert(1)>') + milestone = build(:milestone, title: '<img onerror=alert(1)>', due_date: Date.new(2022, 6, 26)) - expect(helper.sidebar_milestone_tooltip_label(milestone)).to eq('<img onerror=alert(1)><br/>Milestone') + expect(helper.sidebar_milestone_tooltip_label(milestone)).to eq( + '<img onerror=alert(1)><br/>Jun 26, 2022 (<strong>Past due</strong>)' + ) end end diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb index b14789fd5d2..43500d98591 100644 --- a/spec/helpers/page_layout_helper_spec.rb +++ b/spec/helpers/page_layout_helper_spec.rb @@ -270,7 +270,7 @@ RSpec.describe PageLayoutHelper do it 'merges the status properties with the defaults' do is_expected.to eq({ - current_clear_status_after: time.to_s(:iso8601), + current_clear_status_after: time.to_fs(:iso8601), current_availability: 'busy', current_emoji: 'basketball', current_message: 'Some message', diff --git a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb index 773df9b20ee..56fef37f939 100644 --- a/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb +++ b/spec/lib/gitlab/graphql/pagination/keyset/connection_spec.rb @@ -104,7 +104,7 @@ RSpec.describe Gitlab::Graphql::Pagination::Keyset::Connection do let(:nodes) { Project.all.order(Gitlab::Pagination::Keyset::Order.build([column_order_updated_at, column_order_created_at, column_order_id])) } it 'returns the encoded value of the order' do - expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_s(:inspect)) + expect(decoded_cursor(cursor)).to include('updated_at' => project.updated_at.to_fs(:inspect)) end end end diff --git a/spec/lib/gitlab/manifest_import/metadata_spec.rb b/spec/lib/gitlab/manifest_import/metadata_spec.rb index c8158d3e148..17cc0425ca7 100644 --- a/spec/lib/gitlab/manifest_import/metadata_spec.rb +++ b/spec/lib/gitlab/manifest_import/metadata_spec.rb @@ -12,51 +12,98 @@ RSpec.describe Gitlab::ManifestImport::Metadata, :clean_gitlab_redis_shared_stat end describe '#save' do - it 'stores data in Redis with an expiry of EXPIRY_TIME' do - status = described_class.new(user) - repositories_key = 'manifest_import:metadata:user:1:repositories' - group_id_key = 'manifest_import:metadata:user:1:group_id' + let(:status) { described_class.new(user) } + let(:hashtag_repositories_key) { 'manifest_import:metadata:user:{1}:repositories' } + let(:hashtag_group_id_key) { 'manifest_import:metadata:user:{1}:group_id' } + let(:repositories_key) { 'manifest_import:metadata:user:1:repositories' } + let(:group_id_key) { 'manifest_import:metadata:user:1:group_id' } + + subject { status.save(repositories, 2) } - status.save(repositories, 2) + it 'stores data in Redis with an expiry of EXPIRY_TIME' do + subject Gitlab::Redis::SharedState.with do |redis| + expect(redis.ttl(hashtag_repositories_key)).to be_within(5).of(described_class::EXPIRY_TIME) + expect(redis.ttl(hashtag_group_id_key)).to be_within(5).of(described_class::EXPIRY_TIME) expect(redis.ttl(repositories_key)).to be_within(5).of(described_class::EXPIRY_TIME) expect(redis.ttl(group_id_key)).to be_within(5).of(described_class::EXPIRY_TIME) end end + + context 'with `manifest_import_use_hash_tagged_key`feature flag disabled' do + before do + stub_feature_flags(manifest_import_use_hash_tagged_key: false) + end + + it 'only stores Redis String' do + subject + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.ttl(hashtag_repositories_key)).to eq(-2) + expect(redis.ttl(hashtag_group_id_key)).to eq(-2) + expect(redis.ttl(repositories_key)).to be_within(5).of(described_class::EXPIRY_TIME) + expect(redis.ttl(group_id_key)).to be_within(5).of(described_class::EXPIRY_TIME) + end + end + end end describe '#repositories' do - it 'allows repositories to round-trip with symbol keys' do - status = described_class.new(user) + shared_examples 'read repositories' do + it 'allows repositories to round-trip with symbol keys' do + status = described_class.new(user) - status.save(repositories, 2) + status.save(repositories, 2) + + expect(status.repositories).to eq(repositories) + end - expect(status.repositories).to eq(repositories) + it 'uses the fallback when there is nothing in Redis' do + fallback = { manifest_import_repositories: repositories } + status = described_class.new(user, fallback: fallback) + + expect(status.repositories).to eq(repositories) + end end - it 'uses the fallback when there is nothing in Redis' do - fallback = { manifest_import_repositories: repositories } - status = described_class.new(user, fallback: fallback) + context 'with `manifest_import_use_hash_tagged_key` feature flag disabled' do + before do + stub_feature_flags(manifest_import_use_hash_tagged_key: false) + end - expect(status.repositories).to eq(repositories) + it_behaves_like 'read repositories' end + + it_behaves_like 'read repositories' end describe '#group_id' do - it 'returns the group ID as an integer' do - status = described_class.new(user) + shared_examples 'read group_id' do + it 'returns the group ID as an integer' do + status = described_class.new(user) + + status.save(repositories, 2) - status.save(repositories, 2) + expect(status.group_id).to eq(2) + end + + it 'uses the fallback when there is nothing in Redis' do + fallback = { manifest_import_group_id: 3 } + status = described_class.new(user, fallback: fallback) - expect(status.group_id).to eq(2) + expect(status.group_id).to eq(3) + end end - it 'uses the fallback when there is nothing in Redis' do - fallback = { manifest_import_group_id: 3 } - status = described_class.new(user, fallback: fallback) + context 'with feature flag disabled' do + before do + stub_feature_flags(manifest_import_use_hash_tagged_key: false) + end - expect(status.group_id).to eq(3) + it_behaves_like 'read group_id' end + + it_behaves_like 'read group_id' end end diff --git a/spec/lib/gitlab/pagination/keyset/iterator_spec.rb b/spec/lib/gitlab/pagination/keyset/iterator_spec.rb index eee743c5e48..afaad48d363 100644 --- a/spec/lib/gitlab/pagination/keyset/iterator_spec.rb +++ b/spec/lib/gitlab/pagination/keyset/iterator_spec.rb @@ -87,7 +87,7 @@ RSpec.describe Gitlab::Pagination::Keyset::Iterator do time = Time.current iterator.each_batch(of: 2) do |relation| - Issue.connection.execute("UPDATE issues SET updated_at = '#{time.to_s(:inspect)}' WHERE id IN (#{relation.reselect(:id).to_sql})") + Issue.connection.execute("UPDATE issues SET updated_at = '#{time.to_fs(:inspect)}' WHERE id IN (#{relation.reselect(:id).to_sql})") end expect(Issue.pluck(:updated_at)).to all(be_within(5.seconds).of(time)) diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb index 993edcab87d..c22e7a1240f 100644 --- a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -226,6 +226,14 @@ RSpec.describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gi expect(redis_ttl(cookie_key)).to be_within(1).of(expected_ttl) end + it 'does not try to set an invalid ttl at the end of expiry' do + with_redis { |r| r.expire(cookie_key, 1) } + + sleep 0.5 # sleep 500ms to redis would round the remaining ttl to 0 + + expect { subject }.not_to raise_error + end + context 'and low offsets' do let(:existing_cookie) do { diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb index 317929f77e6..eee5396bdbf 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb @@ -30,8 +30,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do let(:expected_value) { 6 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\""\ " WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}'" @@ -63,8 +63,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do let(:expected_value) { 3 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\""\ " WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}'"\ @@ -92,8 +92,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do let(:expected_value) { 3 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\""\ " WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}'"\ @@ -121,8 +121,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do let(:expected_value) { 4 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\""\ " WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}'"\ @@ -150,8 +150,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do let(:expected_value) { 2 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\""\ " WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}'"\ @@ -202,8 +202,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do let(:expected_value) { 3 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ "WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}' " \ @@ -249,8 +249,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'for 28d time frame' do context 'with project entity' do let(:expected_value) { 2 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ "WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}' " \ @@ -265,8 +265,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitie context 'with group entity' do let(:expected_value) { 2 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"bulk_import_entities\".\"id\") FROM \"bulk_import_entities\" " \ "WHERE \"bulk_import_entities\".\"created_at\" BETWEEN '#{start}' AND '#{finish}' " \ diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric_spec.rb index b7da9b27e19..8ae64e8db23 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_metric_spec.rb @@ -43,8 +43,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountImportedProjectsMe context 'for 28d time frame' do let(:expected_value) { 3 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"projects\".\"id\") FROM \"projects\" WHERE \"projects\".\"created_at\""\ " BETWEEN '#{start}' AND '#{finish}' AND \"projects\".\"import_type\" = 'gitea'" @@ -70,8 +70,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountImportedProjectsMe context 'for 28d time frame' do let(:expected_value) { 2 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT COUNT(\"projects\".\"id\") FROM \"projects\" WHERE \"projects\".\"created_at\""\ " BETWEEN '#{start}' AND '#{finish}' AND \"projects\".\"import_type\" = 'bitbucket'" diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb index bfc4240def6..bd432b614e7 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb @@ -45,8 +45,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountImportedProjectsTo context 'for 28d time frame' do let(:expected_value) { 8 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) do "SELECT (SELECT COUNT(\"projects\".\"id\") FROM \"projects\" WHERE \"projects\".\"import_type\""\ " IN ('gitlab_project', 'gitlab', 'github', 'bitbucket', 'bitbucket_server', 'gitea', 'git', 'manifest',"\ diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb index 3fb4c3a4e3f..86aa37b494a 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_issues_metric_spec.rb @@ -16,8 +16,8 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountUsersCreatingIssue context 'for 28d time frame' do let(:expected_value) { 1 } - let(:start) { 30.days.ago.to_s(:db) } - let(:finish) { 2.days.ago.to_s(:db) } + let(:start) { 30.days.ago.to_fs(:db) } + let(:finish) { 2.days.ago.to_fs(:db) } let(:expected_query) { "SELECT COUNT(DISTINCT \"issues\".\"author_id\") FROM \"issues\" WHERE \"issues\".\"created_at\" BETWEEN '#{start}' AND '#{finish}'" } it_behaves_like 'a correct instrumented metric value and query', { time_frame: '28d' } diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 46a876f34e9..01efe66a419 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -356,4 +356,20 @@ RSpec.describe Milestone, 'Milestoneish', factory_default: :keep do expect(milestone.human_total_time_estimate).to be_nil end end + + describe '#expires_at' do + it 'returns the date when milestone expires' do + due_date = Date.today + 1.day + milestone.due_date = due_date + + expect(milestone.expires_at).to eq("expires on #{due_date.to_fs(:medium)}") + end + + it 'returns the date when milestone expires' do + due_date = Date.today - 1.day + milestone.due_date = due_date + + expect(milestone.expires_at).to eq("expired on #{due_date.to_fs(:medium)}") + end + end end diff --git a/spec/models/integration_spec.rb b/spec/models/integration_spec.rb index 9f2c692287e..7fcd74cd37f 100644 --- a/spec/models/integration_spec.rb +++ b/spec/models/integration_spec.rb @@ -1362,5 +1362,17 @@ RSpec.describe Integration, feature_category: :integrations do async_execute end end + + context 'when the Gitlab::SilentMode is enabled' do + before do + allow(Gitlab::SilentMode).to receive(:enabled?).and_return(true) + end + + it 'does not queue a worker' do + expect(Integrations::ExecuteWorker).not_to receive(:perform_async) + + async_execute + end + end end end diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb index e56ff2241b1..05e10fd6763 100644 --- a/spec/models/protected_branch/push_access_level_spec.rb +++ b/spec/models/protected_branch/push_access_level_spec.rb @@ -4,81 +4,6 @@ require 'spec_helper' RSpec.describe ProtectedBranch::PushAccessLevel, feature_category: :source_code_management do include_examples 'protected branch access' + include_examples 'protected ref deploy_key access' include_examples 'protected ref access allowed_access_levels' - - describe 'associations' do - it { is_expected.to belong_to(:deploy_key) } - end - - describe 'validations' do - it 'is not valid when a record exists with the same access level' do - protected_branch = create(:protected_branch) - create(:protected_branch_push_access_level, protected_branch: protected_branch) - level = build(:protected_branch_push_access_level, protected_branch: protected_branch) - - expect(level).to be_invalid - end - - it 'is not valid when a record exists with the same access level' do - protected_branch = create(:protected_branch) - deploy_key = create(:deploy_key, projects: [protected_branch.project]) - create(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key) - level = build(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key) - - expect(level).to be_invalid - end - - it 'checks that a deploy key is enabled for the same project as the protected branch\'s' do - level = build(:protected_branch_push_access_level, deploy_key: create(:deploy_key)) - - expect { level.save! }.to raise_error(ActiveRecord::RecordInvalid) - expect(level.errors.full_messages).to contain_exactly('Deploy key is not enabled for this project') - end - end - - describe '#check_access' do - let_it_be(:project) { create(:project) } - let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_push, project: project) } - let_it_be(:user) { create(:user) } - let_it_be(:deploy_key) { create(:deploy_key, user: user) } - - let!(:deploy_keys_project) { create(:deploy_keys_project, project: project, deploy_key: deploy_key, can_push: can_push) } - let(:can_push) { true } - - before_all do - project.add_maintainer(user) - end - - context 'when this push_access_level is tied to a deploy key' do - let(:push_access_level) { create(:protected_branch_push_access_level, protected_branch: protected_branch, deploy_key: deploy_key) } - - context 'when the deploy key is among the active keys for this project' do - specify do - expect(push_access_level.check_access(user)).to be_truthy - end - end - - context 'when the deploy key is not among the active keys of this project' do - let(:can_push) { false } - - it 'is false' do - expect(push_access_level.check_access(user)).to be_falsey - end - end - end - end - - describe '#type' do - let(:push_level_access) { build(:protected_branch_push_access_level) } - - it 'returns :deploy_key when a deploy key is tied to the protected branch' do - push_level_access.deploy_key = create(:deploy_key) - - expect(push_level_access.type).to eq(:deploy_key) - end - - it 'returns :role by default' do - expect(push_level_access.type).to eq(:role) - end - end end diff --git a/spec/models/protected_tag/create_access_level_spec.rb b/spec/models/protected_tag/create_access_level_spec.rb index 8eeccdc9b34..d795399060e 100644 --- a/spec/models/protected_tag/create_access_level_spec.rb +++ b/spec/models/protected_tag/create_access_level_spec.rb @@ -4,134 +4,6 @@ require 'spec_helper' RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_management do include_examples 'protected tag access' + include_examples 'protected ref deploy_key access' include_examples 'protected ref access allowed_access_levels' - - describe 'associations' do - it { is_expected.to belong_to(:deploy_key) } - end - - describe 'validations', :aggregate_failures do - let_it_be(:protected_tag) { create(:protected_tag) } - - context 'when deploy key enabled for the project' do - let(:deploy_key) { create(:deploy_key, projects: [protected_tag.project]) } - - it 'is valid' do - level = build(:protected_tag_create_access_level, protected_tag: protected_tag, deploy_key: deploy_key) - - expect(level).to be_valid - end - end - - context 'when a record exists with the same access level' do - before do - create(:protected_tag_create_access_level, protected_tag: protected_tag) - end - - it 'is not valid' do - level = build(:protected_tag_create_access_level, protected_tag: protected_tag) - - expect(level).to be_invalid - expect(level.errors.full_messages).to include('Access level has already been taken') - end - end - - context 'when a deploy key already added for this access level' do - let!(:create_access_level) do - create(:protected_tag_create_access_level, protected_tag: protected_tag, deploy_key: deploy_key) - end - - let(:deploy_key) { create(:deploy_key, projects: [protected_tag.project]) } - - it 'is not valid' do - level = build(:protected_tag_create_access_level, protected_tag: protected_tag, deploy_key: deploy_key) - - expect(level).to be_invalid - expect(level.errors.full_messages).to contain_exactly('Deploy key has already been taken') - end - end - - context 'when deploy key is not enabled for the project' do - let(:create_access_level) do - build(:protected_tag_create_access_level, protected_tag: protected_tag, deploy_key: create(:deploy_key)) - end - - it 'returns an error' do - expect(create_access_level).to be_invalid - expect(create_access_level.errors.full_messages).to contain_exactly( - 'Deploy key is not enabled for this project' - ) - end - end - end - - describe '#check_access' do - let_it_be(:project) { create(:project) } - let_it_be(:protected_tag) { create(:protected_tag, :no_one_can_create, project: project) } - let_it_be(:user) { create(:user) } - let_it_be(:deploy_key) { create(:deploy_key, user: user) } - - let!(:deploy_keys_project) do - create(:deploy_keys_project, project: project, deploy_key: deploy_key, can_push: can_push) - end - - let(:create_access_level) { protected_tag.create_access_levels.first } - let(:can_push) { true } - - before_all do - project.add_maintainer(user) - end - - it { expect(create_access_level.check_access(user)).to be_falsey } - - context 'when this create_access_level is tied to a deploy key' do - let(:create_access_level) do - create(:protected_tag_create_access_level, protected_tag: protected_tag, deploy_key: deploy_key) - end - - context 'when the deploy key is among the active keys for this project' do - it { expect(create_access_level.check_access(user)).to be_truthy } - end - - context 'when user is missing' do - it { expect(create_access_level.check_access(nil)).to be_falsey } - end - - context 'when deploy key does not belong to the user' do - let(:another_user) { create(:user) } - - it { expect(create_access_level.check_access(another_user)).to be_falsey } - end - - context 'when user cannot access the project' do - before do - allow(user).to receive(:can?).with(:read_project, project).and_return(false) - end - - it { expect(create_access_level.check_access(user)).to be_falsey } - end - - context 'when the deploy key is not among the active keys of this project' do - let(:can_push) { false } - - it { expect(create_access_level.check_access(user)).to be_falsey } - end - end - end - - describe '#type' do - let(:create_access_level) { build(:protected_tag_create_access_level) } - - it 'returns :role by default' do - expect(create_access_level.type).to eq(:role) - end - - context 'when a deploy key is tied to the protected branch' do - let(:create_access_level) { build(:protected_tag_create_access_level, deploy_key: build(:deploy_key)) } - - it 'returns :deploy_key' do - expect(create_access_level.type).to eq(:deploy_key) - end - end - end end diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb deleted file mode 100644 index 143bc1672f8..00000000000 --- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Getting Metrics Dashboard Annotations', feature_category: :metrics do - include GraphqlHelpers - - let_it_be(:project) { create(:project) } - let_it_be(:environment) { create(:environment, project: project) } - let_it_be(:current_user) { create(:user) } - let_it_be(:path) { 'config/prometheus/common_metrics.yml' } - let_it_be(:from) { "2020-04-01T03:29:25Z" } - let_it_be(:to) { Time.zone.now.advance(minutes: 5) } - let_it_be(:annotation) { create(:metrics_dashboard_annotation, environment: environment, dashboard_path: path) } - let_it_be(:annotation_for_different_env) { create(:metrics_dashboard_annotation, dashboard_path: path) } - let_it_be(:annotation_for_different_dashboard) { create(:metrics_dashboard_annotation, environment: environment, dashboard_path: ".gitlab/dashboards/test.yml") } - let_it_be(:to_old_annotation) do - create(:metrics_dashboard_annotation, environment: environment, starting_at: Time.parse(from).advance(minutes: -5), dashboard_path: path) - end - - let_it_be(:to_new_annotation) do - create(:metrics_dashboard_annotation, environment: environment, starting_at: to.advance(minutes: 5), dashboard_path: path) - end - - let(:remove_monitor_metrics) { false } - let(:args) { "from: \"#{from}\", to: \"#{to}\"" } - let(:fields) do - <<~QUERY - #{all_graphql_fields_for('MetricsDashboardAnnotation'.classify)} - QUERY - end - - let(:query) do - %( - query { - project(fullPath: "#{project.full_path}") { - environments(name: "#{environment.name}") { - nodes { - metricsDashboard(path: "#{path}") { - annotations(#{args}) { - nodes { - #{fields} - } - } - } - } - } - } - } - ) - end - - before do - stub_feature_flags(remove_monitor_metrics: remove_monitor_metrics) - project.add_developer(current_user) - post_graphql(query, current_user: current_user) - end - - it_behaves_like 'a working graphql query' - - it 'returns annotations' do - annotations = graphql_data.dig('project', 'environments', 'nodes')[0].dig('metricsDashboard', 'annotations', 'nodes') - - expect(annotations).to match_array [{ - "description" => annotation.description, - "id" => annotation.to_global_id.to_s, - "panelId" => annotation.panel_xid, - "startingAt" => annotation.starting_at.iso8601, - "endingAt" => nil - }] - end - - context 'arguments' do - context 'from is missing' do - let(:args) { "to: \"#{from}\"" } - - it 'returns error' do - post_graphql(query, current_user: current_user) - - expect(graphql_errors[0]).to include("message" => "Field 'annotations' is missing required arguments: from") - end - end - - context 'to is missing' do - let(:args) { "from: \"#{from}\"" } - - it_behaves_like 'a working graphql query' - end - end - - context 'when metrics dashboard feature is unavailable' do - let(:remove_monitor_metrics) { true } - - it_behaves_like 'a working graphql query' - - it 'returns nil' do - annotations = graphql_data.dig( - 'project', 'environments', 'nodes', 0, 'metricsDashboard', 'annotations' - ) - - expect(annotations).to be_nil - end - end -end diff --git a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb deleted file mode 100644 index b7d9b59f5fe..00000000000 --- a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb +++ /dev/null @@ -1,114 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Getting Metrics Dashboard', feature_category: :metrics do - include GraphqlHelpers - - let_it_be(:current_user) { create(:user) } - - let(:project) { create(:project) } - let(:environment) { create(:environment, project: project) } - - let(:query) do - graphql_query_for( - 'project', { 'fullPath' => project.full_path }, - query_graphql_field( - :environments, { 'name' => environment.name }, - query_graphql_field( - :nodes, nil, - query_graphql_field( - :metricsDashboard, { 'path' => path }, - all_graphql_fields_for('MetricsDashboard'.classify) - ) - ) - ) - ) - end - - context 'for anonymous user' do - before do - post_graphql(query, current_user: current_user) - end - - context 'requested dashboard is available' do - let(:path) { 'config/prometheus/common_metrics.yml' } - - it_behaves_like 'a working graphql query' - - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes') - - expect(dashboard).to be_nil - end - end - end - - context 'for user with developer access' do - let(:remove_monitor_metrics) { false } - - before do - stub_feature_flags(remove_monitor_metrics: remove_monitor_metrics) - project.add_developer(current_user) - post_graphql(query, current_user: current_user) - end - - context 'requested dashboard is available' do - let(:path) { 'config/prometheus/common_metrics.yml' } - - it_behaves_like 'a working graphql query' - - it 'returns metrics dashboard' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil) - end - - context 'invalid dashboard' do - let(:path) { '.gitlab/dashboards/metrics.yml' } - let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) } - - it 'returns metrics dashboard' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"]) - end - end - - context 'empty dashboard' do - let(:path) { '.gitlab/dashboards/metrics.yml' } - let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) } - - it 'returns metrics dashboard' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"]) - end - end - - context 'metrics dashboard feature is unavailable' do - let(:remove_monitor_metrics) { true } - - it_behaves_like 'a working graphql query' - - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to be_nil - end - end - end - - context 'requested dashboard can not be found' do - let(:path) { 'config/prometheus/i_am_not_here.yml' } - - it_behaves_like 'a working graphql query' - - it 'returns nil' do - dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard') - - expect(dashboard).to be_nil - end - end - end -end diff --git a/spec/services/system_notes/time_tracking_service_spec.rb b/spec/services/system_notes/time_tracking_service_spec.rb index 71228050085..a3793880ff1 100644 --- a/spec/services/system_notes/time_tracking_service_spec.rb +++ b/spec/services/system_notes/time_tracking_service_spec.rb @@ -25,7 +25,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann context 'when both dates are added' do it 'sets the correct note message' do - expect(note.note).to eq("changed start date to #{start_date.to_s(:long)} and changed due date to #{due_date.to_s(:long)}") + expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)} and changed due date to #{due_date.to_fs(:long)}") end end @@ -37,7 +37,7 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann end it 'sets the correct note message' do - expect(note.note).to eq("removed start date #{start_date.to_s(:long)} and removed due date #{due_date.to_s(:long)}") + expect(note.note).to eq("removed start date #{start_date.to_fs(:long)} and removed due date #{due_date.to_fs(:long)}") end end @@ -45,14 +45,14 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann let(:changed_dates) { { 'due_date' => [nil, due_date] } } it 'sets the correct note message' do - expect(note.note).to eq("changed due date to #{due_date.to_s(:long)}") + expect(note.note).to eq("changed due date to #{due_date.to_fs(:long)}") end context 'and start date removed' do let(:changed_dates) { { 'due_date' => [nil, due_date], 'start_date' => [start_date, nil] } } it 'sets the correct note message' do - expect(note.note).to eq("removed start date #{start_date.to_s(:long)} and changed due date to #{due_date.to_s(:long)}") + expect(note.note).to eq("removed start date #{start_date.to_fs(:long)} and changed due date to #{due_date.to_fs(:long)}") end end end @@ -73,14 +73,14 @@ RSpec.describe ::SystemNotes::TimeTrackingService, feature_category: :team_plann end it 'sets the correct note message' do - expect(note.note).to eq("changed start date to #{start_date.to_s(:long)}") + expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)}") end context 'and due date removed' do let(:changed_dates) { { 'due_date' => [due_date, nil], 'start_date' => [nil, start_date] } } it 'sets the correct note message' do - expect(note.note).to eq("changed start date to #{start_date.to_s(:long)} and removed due date #{due_date.to_s(:long)}") + expect(note.note).to eq("changed start date to #{start_date.to_fs(:long)} and removed due date #{due_date.to_fs(:long)}") end end end diff --git a/spec/services/work_items/export_csv_service_spec.rb b/spec/services/work_items/export_csv_service_spec.rb index 948ff89245e..4566289231f 100644 --- a/spec/services/work_items/export_csv_service_spec.rb +++ b/spec/services/work_items/export_csv_service_spec.rb @@ -61,7 +61,7 @@ RSpec.describe WorkItems::ExportCsvService, :with_license, feature_category: :te end specify 'created_at' do - expect(csv[0]['Created At (UTC)']).to eq(work_item_1.created_at.to_s(:csv)) + expect(csv[0]['Created At (UTC)']).to eq(work_item_1.created_at.to_fs(:csv)) end specify 'description' do diff --git a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb index 6f104f400bc..52f0e7847b0 100644 --- a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb +++ b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb @@ -2,7 +2,7 @@ RSpec.shared_examples 'StageEventModel' do describe '.upsert_data' do - let(:time) { Time.parse(Time.current.to_s(:db)) } # truncating the timestamp so we can compare it with the timestamp loaded from the DB + let(:time) { Time.parse(Time.current.to_fs(:db)) } # truncating the timestamp so we can compare it with the timestamp loaded from the DB let(:input_data) do [ { diff --git a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb index 4753d7a4556..0e9200f1fd9 100644 --- a/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb +++ b/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb @@ -6,16 +6,34 @@ RSpec.shared_examples 'protected ref access' do |association| let_it_be(:project) { create(:project) } let_it_be(:protected_ref) { create(association, project: project) } # rubocop:disable Rails/SaveBang - it { is_expected.to validate_inclusion_of(:access_level).in_array(described_class.allowed_access_levels) } + describe 'validations' do + subject { build(described_class.model_name.singular) } - it { is_expected.to validate_presence_of(:access_level) } + context 'when role?' do + it { is_expected.to validate_inclusion_of(:access_level).in_array(described_class.allowed_access_levels) } - context 'when not role?' do - before do - allow(subject).to receive(:role?).and_return(false) + it { is_expected.to validate_presence_of(:access_level) } + + it do + is_expected.to validate_uniqueness_of(:access_level) + .scoped_to("#{described_class.module_parent.model_name.singular}_id") + end end - it { is_expected.not_to validate_presence_of(:access_level) } + context 'when not role?' do + before do + allow(subject).to receive(:role?).and_return(false) + end + + it { is_expected.not_to validate_presence_of(:access_level) } + + it { is_expected.not_to validate_inclusion_of(:access_level).in_array(described_class.allowed_access_levels) } + + it do + is_expected.not_to validate_uniqueness_of(:access_level) + .scoped_to("#{described_class.module_parent.model_name.singular}_id") + end + end end describe '::human_access_levels' do diff --git a/spec/support/shared_examples/models/concerns/protected_ref_deploy_key_access_examples.rb b/spec/support/shared_examples/models/concerns/protected_ref_deploy_key_access_examples.rb new file mode 100644 index 00000000000..f2e79dc377b --- /dev/null +++ b/spec/support/shared_examples/models/concerns/protected_ref_deploy_key_access_examples.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'protected ref deploy_key access' do + let_it_be(:described_instance) { described_class.model_name.singular } + let_it_be(:protected_ref_name) { described_class.module_parent.model_name.singular } + let_it_be(:project) { create(:project) } + let_it_be(:protected_ref) { create(protected_ref_name, project: project) } # rubocop:disable Rails/SaveBang + + describe 'associations' do + it { is_expected.to belong_to(:deploy_key) } + end + + describe 'validations' do + context 'when deploy_key?' do + context 'when deploy key enabled for the project' do + let(:deploy_key) do + create(:deploy_keys_project, :write_access, project: project).deploy_key + end + + it 'is valid' do + level = build(described_instance, protected_ref_name => protected_ref, deploy_key: deploy_key) + + expect(level).to be_valid + end + end + + context 'when a deploy key already added for this access level' do + let(:deploy_key) { create(:deploy_keys_project, :write_access, project: project).deploy_key } + + before do + create(described_instance, protected_ref_name => protected_ref, deploy_key: deploy_key) + end + + subject(:access_level) do + build(described_instance, protected_ref_name => protected_ref, deploy_key: deploy_key) + end + + it 'is not valid', :aggregate_failures do + is_expected.to be_invalid + expect(access_level.errors.full_messages).to contain_exactly('Deploy key has already been taken') + end + end + + context 'when deploy key is not enabled for the project' do + subject(:access_level) do + build(described_instance, protected_ref_name => protected_ref, deploy_key: create(:deploy_key)) + end + + it 'is not valid', :aggregate_failures do + is_expected.to be_invalid + expect(access_level.errors.full_messages).to contain_exactly('Deploy key is not enabled for this project') + end + end + + context 'when deploy key is not active for the project' do + subject(:access_level) do + deploy_key = create(:deploy_keys_project, :readonly_access, project: project).deploy_key + build(described_instance, protected_ref_name => protected_ref, deploy_key: deploy_key) + end + + it 'is not valid', :aggregate_failures do + is_expected.to be_invalid + expect(access_level.errors.full_messages).to contain_exactly('Deploy key is not enabled for this project') + end + end + end + end + + describe '#check_access' do + let_it_be(:user) { create(:user) } + let_it_be(:deploy_key) { create(:deploy_key, user: user) } + let_it_be(:deploy_keys_project) do + create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) + end + + before_all do + project.add_maintainer(user) + end + + context "when this #{described_class.model_name.singular} is tied to a deploy key" do + let!(:access_level) do + create(described_instance, protected_ref_name => protected_ref, deploy_key: deploy_key) + end + + context 'when the deploy key is among the active keys for this project' do + it { expect(access_level.check_access(user)).to be_truthy } + end + + context 'when user is missing' do + it { expect(access_level.check_access(nil)).to be_falsey } + end + + context 'when deploy key does not belong to the user' do + let(:another_user) { create(:user) } + + it { expect(access_level.check_access(another_user)).to be_falsey } + end + + context 'when user cannot access the project' do + before do + allow(user).to receive(:can?).with(:read_project, project).and_return(false) + end + + it { expect(access_level.check_access(user)).to be_falsey } + end + + context 'when the deploy key is not among the active keys of this project' do + before do + deploy_keys_project.update!(can_push: false) + end + + after do + deploy_keys_project.update!(can_push: true) + end + + it { expect(access_level.check_access(user)).to be_falsey } + end + end + end + + describe '#type' do + let(:access_level) { build(described_instance) } + + context 'when deploy_key?' do + let(:access_level) { build(described_instance, deploy_key: build(:deploy_key)) } + + it 'returns :deploy_key' do + expect(access_level.type).to eq(:deploy_key) + end + end + end +end diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb index ea0a9ebb02c..e88b1bf4053 100644 --- a/spec/views/profiles/show.html.haml_spec.rb +++ b/spec/views/profiles/show.html.haml_spec.rb @@ -46,7 +46,7 @@ RSpec.describe 'profiles/show' do ) expect(rendered).to have_field( 'user[status][clear_status_after]', - with: user_status.clear_status_at.to_s(:iso8601), + with: user_status.clear_status_at.to_fs(:iso8601), type: :hidden ) end diff --git a/spec/workers/integrations/execute_worker_spec.rb b/spec/workers/integrations/execute_worker_spec.rb index 717e3c65820..369fc5fd091 100644 --- a/spec/workers/integrations/execute_worker_spec.rb +++ b/spec/workers/integrations/execute_worker_spec.rb @@ -36,4 +36,18 @@ RSpec.describe Integrations::ExecuteWorker, '#perform', feature_category: :integ end.not_to raise_error end end + + context 'when the Gitlab::SilentMode is enabled' do + before do + allow(Gitlab::SilentMode).to receive(:enabled?).and_return(true) + end + + it 'completes silently and does not log an error' do + expect(Gitlab::IntegrationsLogger).not_to receive(:error) + + expect do + worker.perform(non_existing_record_id, {}) + end.not_to raise_error + end + end end |