diff options
Diffstat (limited to 'spec')
19 files changed, 498 insertions, 447 deletions
diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb b/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb index e15336f586e..54d0bfef9dd 100644 --- a/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb +++ b/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountFooMetric do +RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountFooMetric, feature_category: :service_ping do let(:expected_value) { 1 } it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' } diff --git a/spec/frontend/ci/runner/components/runner_create_form_spec.js b/spec/frontend/ci/runner/components/runner_create_form_spec.js index f11667ee415..c452e32b0e4 100644 --- a/spec/frontend/ci/runner/components/runner_create_form_spec.js +++ b/spec/frontend/ci/runner/components/runner_create_form_spec.js @@ -11,6 +11,7 @@ import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE, + I18N_CREATE_ERROR, } from '~/ci/runner/constants'; import runnerCreateMutation from '~/ci/runner/graphql/new/runner_create.mutation.graphql'; import { captureException } from '~/ci/runner/sentry_utils'; @@ -188,5 +189,37 @@ describe('RunnerCreateForm', () => { expect(captureException).not.toHaveBeenCalled(); }); }); + + describe('when no runner information is returned', () => { + beforeEach(async () => { + runnerCreateHandler.mockResolvedValue({ + data: { + runnerCreate: { + errors: [], + runner: null, + }, + }, + }); + + findForm().vm.$emit('submit', { preventDefault }); + await waitForPromises(); + }); + + it('emits "error" result', () => { + expect(wrapper.emitted('error')[0]).toEqual([new TypeError(I18N_CREATE_ERROR)]); + }); + + it('does not show a saving state', () => { + expect(findSubmitBtn().props('loading')).toBe(false); + }); + + it('reports error', () => { + expect(captureException).toHaveBeenCalledTimes(1); + expect(captureException).toHaveBeenCalledWith({ + component: 'RunnerCreateForm', + error: new Error(I18N_CREATE_ERROR), + }); + }); + }); }); }); diff --git a/spec/frontend/ci/runner/components/runner_managers_detail_spec.js b/spec/frontend/ci/runner/components/runner_managers_detail_spec.js index b2551e67396..3435292394f 100644 --- a/spec/frontend/ci/runner/components/runner_managers_detail_spec.js +++ b/spec/frontend/ci/runner/components/runner_managers_detail_spec.js @@ -2,16 +2,12 @@ import { GlCollapse, GlSkeletonLoader, GlTableLite } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { __ } from '~/locale'; -import { - shallowMountExtended, - mountExtended, - extendedWrapper, -} from 'helpers/vue_test_utils_helper'; +import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import RunnerManagersDetail from '~/ci/runner/components/runner_managers_detail.vue'; -import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import RunnerManagersTable from '~/ci/runner/components/runner_managers_table.vue'; import runnerManagersQuery from '~/ci/runner/graphql/show/runner_managers.query.graphql'; import { runnerData, runnerManagersData } from '../mock_data'; @@ -33,8 +29,7 @@ describe('RunnerJobs', () => { const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader); const findCollapse = () => wrapper.findComponent(GlCollapse); - const findRows = () => wrapper.findAll('tbody tr'); - const findCell = ({ field, i }) => extendedWrapper(findRows().at(i)).findByTestId(`td-${field}`); + const findRunnerManagersTable = () => wrapper.findComponent(RunnerManagersTable); const createComponent = ({ props, mountFn = shallowMountExtended } = {}) => { wrapper = mountFn(RunnerManagersDetail, { @@ -162,21 +157,7 @@ describe('RunnerJobs', () => { it('shows rows', () => { expect(findCollapse().attributes('visible')).toBe('true'); - expect(findRows()).toHaveLength(mockRunnerManagers.length); - }); - - it('shows system id', () => { - expect(findCell({ field: 'systemId', i: 0 }).text()).toBe(mockRunnerManagers[0].systemId); - expect(findCell({ field: 'systemId', i: 1 }).text()).toBe(mockRunnerManagers[1].systemId); - }); - - it('shows contacted at', () => { - expect(findCell({ field: 'contactedAt', i: 0 }).findComponent(TimeAgo).props('time')).toBe( - mockRunnerManagers[0].contactedAt, - ); - expect(findCell({ field: 'contactedAt', i: 1 }).findComponent(TimeAgo).props('time')).toBe( - mockRunnerManagers[1].contactedAt, - ); + expect(findRunnerManagersTable().props('items')).toEqual(mockRunnerManagers); }); it('collapses when clicked', async () => { diff --git a/spec/frontend/ci/runner/components/runner_managers_table_spec.js b/spec/frontend/ci/runner/components/runner_managers_table_spec.js index a0ebf3b2578..e72ea60cdbd 100644 --- a/spec/frontend/ci/runner/components/runner_managers_table_spec.js +++ b/spec/frontend/ci/runner/components/runner_managers_table_spec.js @@ -1,20 +1,17 @@ import { GlTableLite } from '@gitlab/ui'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; import { s__ } from '~/locale'; import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper'; import RunnerManagersTable from '~/ci/runner/components/runner_managers_table.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import { I18N_STATUS_NEVER_CONTACTED } from '~/ci/runner/constants'; import { runnerManagersData } from '../mock_data'; jest.mock('~/alert'); jest.mock('~/ci/runner/sentry_utils'); -const [runnerManager1, runnerManager2] = runnerManagersData.data.runner.managers.nodes; - -Vue.use(VueApollo); +const mockItems = runnerManagersData.data.runner.managers.nodes; describe('RunnerJobs', () => { let wrapper; @@ -25,9 +22,11 @@ describe('RunnerJobs', () => { const findCellText = (opts) => findCell(opts).text().replace(/\s+/g, ' '); const createComponent = ({ item } = {}) => { + const [mockItem, ...otherItems] = mockItems; + wrapper = mountExtended(RunnerManagersTable, { propsData: { - items: [{ ...runnerManager1, ...item }, runnerManager2], + items: [{ ...mockItem, ...item }, ...otherItems], }, stubs: { GlTableLite, @@ -54,8 +53,8 @@ describe('RunnerJobs', () => { it('shows system id', () => { createComponent(); - expect(findCellText({ field: 'systemId', i: 0 })).toBe(runnerManager1.systemId); - expect(findCellText({ field: 'systemId', i: 1 })).toBe(runnerManager2.systemId); + expect(findCellText({ field: 'systemId', i: 0 })).toBe(mockItems[0].systemId); + expect(findCellText({ field: 'systemId', i: 1 })).toBe(mockItems[1].systemId); }); it('shows version', () => { @@ -74,6 +73,14 @@ describe('RunnerJobs', () => { expect(findCellText({ field: 'version', i: 0 })).toBe('1.0 (123456)'); }); + it('shows revision without version', () => { + createComponent({ + item: { version: null, revision: '123456' }, + }); + + expect(findCellText({ field: 'version', i: 0 })).toBe('(123456)'); + }); + it('shows ip address', () => { createComponent({ item: { ipAddress: '127.0.0.1' }, @@ -117,7 +124,14 @@ describe('RunnerJobs', () => { it('shows contacted at', () => { createComponent(); expect(findCell({ field: 'contactedAt', i: 0 }).findComponent(TimeAgo).props('time')).toBe( - runnerManager1.contactedAt, + mockItems[0].contactedAt, ); }); + + it('shows missing contacted at', () => { + createComponent({ + item: { contactedAt: null }, + }); + expect(findCellText({ field: 'contactedAt', i: 0 })).toBe(I18N_STATUS_NEVER_CONTACTED); + }); }); diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js index c3e0818fc11..ca65d87f86c 100644 --- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js +++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js @@ -1,4 +1,4 @@ -import { GlDropdown, GlButton } from '@gitlab/ui'; +import { GlDisclosureDropdown, GlButton } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import { nextTick } from 'vue/'; import stubChildren from 'helpers/stub_children'; @@ -19,7 +19,7 @@ describe('Package Files', () => { const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"]'); const findFirstRowFileIcon = () => findFirstRow().findComponent(FileIcon); const findFirstRowCreatedAt = () => findFirstRow().findComponent(TimeAgoTooltip); - const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown); + const findFirstActionMenu = () => findFirstRow().findComponent(GlDisclosureDropdown); const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]'); const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton); const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`); @@ -159,7 +159,7 @@ describe('Package Files', () => { it('emits a delete event when clicked', () => { createComponent(); - findActionMenuDelete().vm.$emit('click'); + findActionMenuDelete().vm.$emit('action'); const [[{ id }]] = wrapper.emitted('delete-file'); expect(id).toBe(npmFiles[0].id); diff --git a/spec/graphql/mutations/achievements/delete_user_achievement_spec.rb b/spec/graphql/mutations/achievements/delete_user_achievement_spec.rb new file mode 100644 index 00000000000..d36b93bd3ea --- /dev/null +++ b/spec/graphql/mutations/achievements/delete_user_achievement_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Achievements::DeleteUserAchievement, feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:maintainer) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) } + + describe '#resolve' do + subject(:resolve_mutation) do + described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve( + user_achievement_id: user_achievement&.to_global_id + ) + end + + before_all do + group.add_maintainer(maintainer) + group.add_owner(owner) + end + + context 'when the user does not have permission' do + let(:current_user) { maintainer } + + it 'raises an error' do + expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + .with_message(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end + + context 'when the user has permission' do + let(:current_user) { owner } + + context 'when the params are invalid' do + let(:user_achievement) { nil } + + it 'returns the validation error' do + expect { resolve_mutation }.to raise_error { Gitlab::Graphql::Errors::ArgumentError } + end + end + + it 'deletes user_achievement' do + resolve_mutation + + expect(Achievements::UserAchievement.find_by(id: user_achievement.id)).to be_nil + end + end + end + + specify { expect(described_class).to require_graphql_authorizations(:destroy_user_achievement) } +end diff --git a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb index 0afd3201853..517ba4d7699 100644 --- a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb +++ b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb @@ -280,7 +280,7 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat describe 'Creating known event entry' do let(:time_frames) { %w[7d 28d] } - let(:expected_known_events) { [{ "name" => event, "aggregation" => "weekly" }] } + let(:expected_known_events) { [{ "name" => event }] } it 'creates a metric definition file using the template' do described_class.new([], options).invoke_all diff --git a/spec/lib/gitlab/middleware/compressed_json_spec.rb b/spec/lib/gitlab/middleware/compressed_json_spec.rb index 5978b2422e0..c0e54c89222 100644 --- a/spec/lib/gitlab/middleware/compressed_json_spec.rb +++ b/spec/lib/gitlab/middleware/compressed_json_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Middleware::CompressedJson do +RSpec.describe Gitlab::Middleware::CompressedJson, feature_category: :shared do let_it_be(:decompressed_input) { '{"foo": "bar"}' } let_it_be(:input) { ActiveSupport::Gzip.compress(decompressed_input) } @@ -70,24 +70,6 @@ RSpec.describe Gitlab::Middleware::CompressedJson do end describe '#call' do - context 'with collector route' do - let(:path) { '/api/v4/error_tracking/collector/1/store' } - - it_behaves_like 'decompress middleware' - - context 'with no Content-Type' do - let(:content_type) { nil } - - it_behaves_like 'decompress middleware' - end - - include_context 'with relative url' do - let(:path) { "#{relative_url_root}/api/v4/error_tracking/collector/1/store" } - - it_behaves_like 'decompress middleware' - end - end - context 'with packages route' do context 'with instance level endpoint' do context 'with npm advisory bulk url' do @@ -192,11 +174,11 @@ RSpec.describe Gitlab::Middleware::CompressedJson do it_behaves_like 'passes input' end - context 'payload is too large' do + context 'when payload is too large' do let(:body_limit) { Gitlab::Middleware::CompressedJson::MAXIMUM_BODY_SIZE } let(:decompressed_input) { 'a' * (body_limit + 100) } let(:input) { ActiveSupport::Gzip.compress(decompressed_input) } - let(:path) { '/api/v4/error_tracking/collector/1/envelope' } + let(:path) { '/api/v4/packages/npm/-/npm/v1/security/advisories/bulk' } it 'reads only limited size' do expect(middleware.call(env)) diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb index b962757c35b..50fb9f9df6e 100644 --- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb +++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb @@ -23,91 +23,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s described_class.clear_memoization(:known_events) end - describe '.track_event' do - # ToDo: remove during https://gitlab.com/groups/gitlab-org/-/epics/9542 cleanup - describe 'daily to weekly key migration precautions' do - let(:event_a_name) { 'example_event_a' } - let(:event_b_name) { 'example_event_b' } - let(:known_events) do - [ - { name: event_a_name, aggregation: 'daily' }, - { name: event_b_name, aggregation: 'weekly' } - ].map(&:with_indifferent_access) - end - - let(:start_date) { (Date.current - 1.week).beginning_of_week } - let(:end_date) { Date.current } - - let(:daily_event) { known_events.first } - let(:daily_key) { described_class.send(:redis_key, daily_event, start_date) } - let(:weekly_key) do - weekly_event = known_events.first.merge(aggregation: 'weekly') - described_class.send(:redis_key, weekly_event, start_date) - end - - before do - allow(described_class).to receive(:load_events).with(described_class::KNOWN_EVENTS_PATH).and_return(known_events) - allow(described_class).to receive(:load_events).with(/ee/).and_return([]) - end - - shared_examples 'writes daily events to daily and weekly keys' do - it :aggregate_failures do - expect(Gitlab::Redis::HLL).to receive(:add).with(expiry: 29.days, key: daily_key, value: 1).and_call_original - expect(Gitlab::Redis::HLL).to receive(:add).with(expiry: 6.weeks, key: weekly_key, value: 1).and_call_original - - described_class.track_event(event_a_name, values: 1, time: start_date) - end - end - - context 'when revert_daily_hll_events_to_weekly_aggregation FF is disabled' do - before do - stub_feature_flags(revert_daily_hll_events_to_weekly_aggregation: false) - end - - it_behaves_like 'writes daily events to daily and weekly keys' - - it 'aggregates weekly for daily keys', :aggregate_failures do - expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [weekly_key]).and_call_original - expect(Gitlab::Redis::HLL).not_to receive(:count).with(keys: [daily_key]).and_call_original - - described_class.unique_events(event_names: [event_a_name], start_date: start_date, end_date: end_date) - end - - it 'does not persists changes to event aggregation attribute' do - described_class.unique_events(event_names: [event_a_name], start_date: start_date, end_date: end_date) - - expect(described_class.known_events.find { |e| e[:name] == event_a_name }[:aggregation]) - .to eql 'daily' - end - end - - context 'when revert_daily_hll_events_to_weekly_aggregation FF is enabled' do - before do - stub_feature_flags(revert_daily_hll_events_to_weekly_aggregation: true) - end - - # we want to write events no matter of the feature state - it_behaves_like 'writes daily events to daily and weekly keys' - - it 'aggregates daily for daily keys', :aggregate_failures do - expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [daily_key]).and_call_original - expect(Gitlab::Redis::HLL).not_to receive(:count).with(keys: [weekly_key]).and_call_original - - described_class.unique_events(event_names: [event_a_name], start_date: start_date, end_date: start_date) - end - end - end - end - describe '.known_events' do let(:ce_temp_dir) { Dir.mktmpdir } let(:ce_temp_file) { Tempfile.new(%w[common .yml], ce_temp_dir) } - let(:ce_event) do - { - "name" => "ce_event", - "aggregation" => "weekly" - } - end + let(:ce_event) { { "name" => "ce_event" } } before do stub_const("#{described_class}::KNOWN_EVENTS_PATH", File.expand_path('*.yml', ce_temp_dir)) @@ -144,13 +63,13 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s let(:known_events) do [ - { name: weekly_event, aggregation: "weekly" }, - { name: daily_event, aggregation: "daily" }, - { name: category_productivity_event, aggregation: "weekly" }, - { name: compliance_slot_event, aggregation: "weekly" }, - { name: no_slot, aggregation: "daily" }, - { name: different_aggregation, aggregation: "monthly" }, - { name: context_event, aggregation: 'weekly' } + { name: weekly_event }, + { name: daily_event }, + { name: category_productivity_event }, + { name: compliance_slot_event }, + { name: no_slot }, + { name: different_aggregation }, + { name: context_event } ].map(&:with_indifferent_access) end @@ -203,15 +122,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s it 'tracks events with multiple values' do values = [entity1, entity2] expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/, value: values, - expiry: described_class::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH) + expiry: described_class::KEY_EXPIRY_LENGTH) described_class.track_event(:g_analytics_contribution, values: values) end - it "raise error if metrics don't have same aggregation" do - expect { described_class.track_event(different_aggregation, values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownAggregation) - end - it 'raise error if metrics of unknown event' do expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent) end @@ -248,22 +163,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s expect(keys).not_to be_empty keys.each do |key| - expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH) - end - end - end - end - - context 'for daily events' do - it 'sets the keys in Redis to expire' do - described_class.track_event("no_slot", values: entity1) - - Gitlab::Redis::SharedState.with do |redis| - keys = redis.scan_each(match: "*_no_slot").to_a - expect(keys).not_to be_empty - - keys.each do |key| - expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::DEFAULT_DAILY_KEY_EXPIRY_LENGTH) + expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::KEY_EXPIRY_LENGTH) end end end @@ -285,7 +185,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s values = [entity1, entity2] expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/, value: values, - expiry: described_class::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH) + expiry: described_class::KEY_EXPIRY_LENGTH) described_class.track_event_in_context(:g_analytics_contribution, values: values, context: default_context) end @@ -347,12 +247,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s expect(described_class.unique_events(event_names: [weekly_event], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1) end - it "raise error if metrics don't have same aggregation" do - expect do - described_class.unique_events(event_names: [daily_event, weekly_event], start_date: 4.weeks.ago, end_date: Date.current) - end.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::AggregationMismatch) - end - context 'when data for the last complete week' do it { expect(described_class.unique_events(event_names: [weekly_event], start_date: 1.week.ago, end_date: Date.current)).to eq(1) } end @@ -369,12 +263,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s it { expect(described_class.unique_events(event_names: [weekly_event.to_sym], start_date: 4.weeks.ago, end_date: 3.weeks.ago)).to eq(1) } end - context 'when using daily aggregation' do - it { expect(described_class.unique_events(event_names: [daily_event], start_date: 7.days.ago, end_date: Date.current)).to eq(2) } - it { expect(described_class.unique_events(event_names: [daily_event], start_date: 28.days.ago, end_date: Date.current)).to eq(3) } - it { expect(described_class.unique_events(event_names: [daily_event], start_date: 28.days.ago, end_date: 21.days.ago)).to eq(1) } - end - context 'when no slot is set' do it { expect(described_class.unique_events(event_names: [no_slot], start_date: 7.days.ago, end_date: Date.current)).to eq(1) } end @@ -388,7 +276,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end end - describe '.weekly_redis_keys' do + describe '.keys_for_aggregation' do using RSpec::Parameterized::TableSyntax let(:weekly_event) { 'i_search_total' } @@ -398,7 +286,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s let(:week_three) { "{#{described_class::REDIS_SLOT}}_i_search_total-2021-01" } let(:week_four) { "{#{described_class::REDIS_SLOT}}_i_search_total-2021-02" } - subject(:weekly_redis_keys) { described_class.send(:weekly_redis_keys, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) } + subject(:keys_for_aggregation) { described_class.send(:keys_for_aggregation, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) } where(:start_date, :end_date, :keys) do '2020-12-21' | '2020-12-21' | [] @@ -421,11 +309,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s end it 'returns 1 key for last for week' do - expect(described_class.send(:weekly_redis_keys, events: [redis_event], start_date: 7.days.ago.to_date, end_date: Date.current).size).to eq 1 + expect(described_class.send(:keys_for_aggregation, events: [redis_event], start_date: 7.days.ago.to_date, end_date: Date.current).size).to eq 1 end it 'returns 4 key for last for weeks' do - expect(described_class.send(:weekly_redis_keys, events: [redis_event], start_date: 4.weeks.ago.to_date, end_date: Date.current).size).to eq 4 + expect(described_class.send(:keys_for_aggregation, events: [redis_event], start_date: 4.weeks.ago.to_date, end_date: Date.current).size).to eq 4 end end @@ -434,9 +322,9 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s let(:known_events) do [ - { name: 'event_name_1', aggregation: "weekly" }, - { name: 'event_name_2', aggregation: "weekly" }, - { name: 'event_name_3', aggregation: "weekly" } + { name: 'event_name_1' }, + { name: 'event_name_2' }, + { name: 'event_name_3' } ].map(&:with_indifferent_access) end @@ -475,11 +363,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s let(:time_range) { { start_date: 7.days.ago, end_date: DateTime.current } } let(:known_events) do [ - { name: 'event1_slot', aggregation: "weekly" }, - { name: 'event2_slot', aggregation: "weekly" }, - { name: 'event3_slot', aggregation: "weekly" }, - { name: 'event5_slot', aggregation: "daily" }, - { name: 'event4', aggregation: "weekly" } + { name: 'event1_slot' }, + { name: 'event2_slot' }, + { name: 'event3_slot' }, + { name: 'event5_slot' }, + { name: 'event4' } ].map(&:with_indifferent_access) end diff --git a/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb b/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb index d33a80b5e64..b4146761aa2 100644 --- a/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb +++ b/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb @@ -10,17 +10,13 @@ RSpec.describe ReMigrateRedisSlotKeys, :migration, feature_category: :service_pi [ { redis_slot: 'management', - aggregation: 'daily', name: 'g_project_management_epic_closed' }, { - aggregation: 'weekly', - name: 'incident_management_incident_assigned' + name: 'incident_management_incident_assigned' # weekly event }, { - aggregation: 'weekly', name: 'non_existing_event' }, { - aggregation: 'weekly', name: 'event_without_expiry' } ] @@ -32,7 +28,7 @@ RSpec.describe ReMigrateRedisSlotKeys, :migration, feature_category: :service_pi .and_return(known_events) expiry_daily = 29.days - expiry_weekly = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH + expiry_weekly = described_class::KEY_EXPIRY_LENGTH default_slot = Gitlab::UsageDataCounters::HLLRedisCounter::REDIS_SLOT diff --git a/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb b/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb index 21cdb6afa24..b5bf55f0d86 100644 --- a/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb +++ b/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb @@ -15,29 +15,29 @@ RSpec.describe MigrateDailyRedisHllEventsToWeeklyAggregation, :migration, :clean context 'with daily aggregation' do let(:date_formatted) { date.strftime('%G-%j') } - let(:event) { { aggregation: 'daily', name: 'wiki_action' } } + let(:event) { { name: 'g_edit_by_web_ide' } } it 'returns correct key' do - existing_key = "#{date_formatted}-{hll_counters}_wiki_action" + existing_key = "#{date_formatted}-{hll_counters}_g_edit_by_web_ide" - expect(described_class.new.redis_key(event, date, event[:aggregation])).to eq(existing_key) + expect(described_class.new.redis_key(event, date, :daily)).to eq(existing_key) end end context 'with weekly aggregation' do let(:date_formatted) { date.strftime('%G-%V') } - let(:event) { { aggregation: 'weekly', name: 'weekly_action' } } + let(:event) { { name: 'weekly_action' } } it 'returns correct key' do existing_key = "{hll_counters}_weekly_action-#{date_formatted}" - expect(described_class.new.redis_key(event, date, event[:aggregation])).to eq(existing_key) + expect(described_class.new.redis_key(event, date, :weekly)).to eq(existing_key) end end end context 'with weekly events' do - let(:events) { [{ aggregation: 'weekly', name: 'weekly_action' }] } + let(:events) { [{ name: 'weekly_action' }] } before do allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return(events) @@ -55,13 +55,12 @@ RSpec.describe MigrateDailyRedisHllEventsToWeeklyAggregation, :migration, :clean context 'with daily events' do let(:daily_expiry) { 29.days } - let(:weekly_expiry) { Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH } + let(:weekly_expiry) { Gitlab::UsageDataCounters::HLLRedisCounter::KEY_EXPIRY_LENGTH } it 'migrates with correct parameters', :aggregate_failures do - events = [{ aggregation: 'daily', name: 'g_project_management_epic_blocked_removed' }] - allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return(events) + event = { name: 'g_project_management_epic_blocked_removed' } + allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return([event]) - event = events.first.dup.tap { |e| e[:aggregation] = 'weekly' } # For every day in the last 30 days, add a value to the daily key with daily expiry (including today) 31.times do |i| key = described_class.new.send(:redis_key, event, Date.today - i.days, :weekly) diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index bca38fa5638..fcde094939a 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -1762,6 +1762,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify { is_expected.to be_allowed(:read_achievement) } specify { is_expected.to be_allowed(:admin_achievement) } specify { is_expected.to be_allowed(:award_achievement) } + specify { is_expected.to be_allowed(:destroy_user_achievement) } context 'with feature flag disabled' do before do @@ -1771,6 +1772,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify { is_expected.to be_disallowed(:read_achievement) } specify { is_expected.to be_disallowed(:admin_achievement) } specify { is_expected.to be_disallowed(:award_achievement) } + specify { is_expected.to be_disallowed(:destroy_user_achievement) } end context 'when current user can not see the group' do @@ -1778,6 +1780,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do specify { is_expected.to be_allowed(:read_achievement) } end + + context 'when current user is not an owner' do + let(:current_user) { maintainer } + + specify { is_expected.to be_disallowed(:destroy_user_achievement) } + end end describe 'admin_package ability' do diff --git a/spec/requests/api/admin/migrations_spec.rb b/spec/requests/api/admin/migrations_spec.rb new file mode 100644 index 00000000000..fc464300b56 --- /dev/null +++ b/spec/requests/api/admin/migrations_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Admin::Migrations, feature_category: :database do + let(:admin) { create(:admin) } + + describe 'POST /admin/migrations/:version/mark' do + let(:database) { :main } + let(:params) { { database: database } } + let(:connection) { ApplicationRecord.connection } + let(:path) { "/admin/migrations/#{version}/mark" } + let(:version) { 1 } + + subject(:mark) do + post api(path, admin, admin_mode: true), params: params + end + + context 'when the migration exists' do + before do + double = instance_double( + Database::MarkMigrationService, + execute: ServiceResponse.success) + + allow(Database::MarkMigrationService) + .to receive(:new) + .with(connection: connection, version: version) + .and_return(double) + end + + it_behaves_like "POST request permissions for admin mode" + + it 'marks the migration as successful' do + mark + + expect(response).to have_gitlab_http_status(:created) + end + end + + context 'when the migration does not exist' do + let(:version) { 123 } + + it 'returns 404' do + mark + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when the migration was already executed' do + let(:version) { connection.migration_context.current_version } + + it 'returns 422' do + mark + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end + end + + context 'when multiple database is enabled' do + let(:ci_model) { Ci::ApplicationRecord } + let(:database) { :ci } + + before do + skip_if_multiple_databases_not_setup(:ci) + end + + it 'uses the correct connection' do + expect(Database::MarkMigrationService) + .to receive(:new) + .with(connection: ci_model.connection, version: version) + .and_call_original + + mark + end + + context 'when the database name does not exist' do + let(:database) { :wrong_database } + + it 'returns bad request', :aggregate_failures do + mark + + expect(response).to have_gitlab_http_status(:bad_request) + expect(response.body).to include('database does not have a valid value') + end + end + end + end +end diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb deleted file mode 100644 index 6a3e71bc859..00000000000 --- a/spec/requests/api/error_tracking/collector_spec.rb +++ /dev/null @@ -1,233 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe API::ErrorTracking::Collector, feature_category: :error_tracking do - let_it_be(:project) { create(:project, :private) } - let_it_be(:setting) { create(:project_error_tracking_setting, :integrated, project: project) } - let_it_be(:client_key) { create(:error_tracking_client_key, project: project) } - - RSpec.shared_examples 'not found' do - it 'reponds with 404' do - subject - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - RSpec.shared_examples 'bad request' do - it 'responds with 400' do - subject - - expect(response).to have_gitlab_http_status(:bad_request) - end - end - - RSpec.shared_examples 'successful request' do - it 'writes to the database and returns OK', :aggregate_failures do - expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1) - expect(response).to have_gitlab_http_status(:ok) - end - end - - describe "POST /error_tracking/collector/api/:id/envelope" do - let_it_be(:raw_event) { fixture_file('error_tracking/event.txt') } - let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/envelope" } - - let(:params) { raw_event } - let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } } - - subject { post api(url), params: params, headers: headers } - - it_behaves_like 'successful request' - - context 'intergrated error tracking feature flag is disabled' do - before do - stub_feature_flags(integrated_error_tracking: false) - end - - it_behaves_like 'not found' - end - - context 'error tracking feature is disabled' do - before do - setting.update!(enabled: false) - end - - it_behaves_like 'not found' - end - - context 'integrated error tracking is disabled' do - before do - setting.update!(integrated: false) - end - - it_behaves_like 'not found' - end - - context 'auth headers are missing' do - let(:headers) { {} } - - it_behaves_like 'bad request' - end - - context 'public key is wrong' do - let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=glet_1fedb514e17f4b958435093deb02048c" } } - - it_behaves_like 'not found' - end - - context 'public key is inactive' do - let(:client_key) { create(:error_tracking_client_key, :disabled, project: project) } - - it_behaves_like 'not found' - end - - context 'empty body' do - let(:params) { '' } - - it_behaves_like 'bad request' - end - - context 'unknown request type' do - let(:params) { fixture_file('error_tracking/unknown.txt') } - - it_behaves_like 'bad request' - end - - context 'transaction request type' do - let(:params) { fixture_file('error_tracking/transaction.txt') } - - it 'does nothing and returns ok' do - expect { subject }.not_to change { ErrorTracking::ErrorEvent.count } - - expect(response).to have_gitlab_http_status(:ok) - end - end - - context 'gzip body' do - let(:standard_headers) do - { - 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}", - 'HTTP_CONTENT_ENCODING' => 'gzip' - } - end - - let(:params) { ActiveSupport::Gzip.compress(raw_event) } - - context 'with application/x-sentry-envelope Content-Type' do - let(:headers) { standard_headers.merge({ 'CONTENT_TYPE' => 'application/x-sentry-envelope' }) } - - it_behaves_like 'successful request' - end - - context 'with unexpected Content-Type' do - let(:headers) { standard_headers.merge({ 'CONTENT_TYPE' => 'application/gzip' }) } - - it 'responds with 415' do - subject - - expect(response).to have_gitlab_http_status(:unsupported_media_type) - end - end - end - end - - describe "POST /error_tracking/collector/api/:id/store" do - let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event.json') } - let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/store" } - - let(:params) { raw_event } - let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } } - - subject { post api(url), params: params, headers: headers } - - it_behaves_like 'successful request' - - context 'empty headers' do - let(:headers) { {} } - - it_behaves_like 'bad request' - end - - context 'empty body' do - let(:params) { '' } - - it_behaves_like 'bad request' - end - - context 'body with string instead of json' do - let(:params) { '"********"' } - - it_behaves_like 'bad request' - end - - context 'collector fails with validation error' do - before do - allow(::ErrorTracking::CollectErrorService) - .to receive(:new).and_raise(Gitlab::ErrorTracking::ErrorRepository::DatabaseError) - end - - it_behaves_like 'bad request' - end - - context 'with platform field too long' do - let(:params) do - event = Gitlab::Json.parse(raw_event) - event['platform'] = 'a' * 256 - Gitlab::Json.dump(event) - end - - it_behaves_like 'bad request' - end - - context 'gzip body' do - let(:headers) do - { - 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}", - 'HTTP_CONTENT_ENCODING' => 'gzip', - 'CONTENT_TYPE' => 'application/json' - } - end - - let(:params) { ActiveSupport::Gzip.compress(raw_event) } - - it_behaves_like 'successful request' - end - - context 'body contains nullbytes' do - let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event_nullbytes.json') } - - it_behaves_like 'successful request' - end - - context 'when JSON key transaction is empty string' do - let_it_be(:raw_event) { fixture_file('error_tracking/php_empty_transaction.json') } - - it_behaves_like 'successful request' - end - - context 'sentry_key as param and empty headers' do - let(:url) { "/error_tracking/collector/api/#{project.id}/store?sentry_key=#{sentry_key}" } - let(:headers) { {} } - - context 'key is wrong' do - let(:sentry_key) { 'glet_1fedb514e17f4b958435093deb02048c' } - - it_behaves_like 'not found' - end - - context 'key is empty' do - let(:sentry_key) { '' } - - it_behaves_like 'bad request' - end - - context 'key is correct' do - let(:sentry_key) { client_key.public_key } - - it_behaves_like 'successful request' - end - end - end -end diff --git a/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb b/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb new file mode 100644 index 00000000000..f759e6dce08 --- /dev/null +++ b/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Achievements::DeleteUserAchievement, feature_category: :user_profile do + include GraphqlHelpers + + let_it_be(:maintainer) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) } + + let(:mutation) { graphql_mutation(:user_achievements_delete, params) } + let(:user_achievement_id) { user_achievement&.to_global_id } + let(:params) { { user_achievement_id: user_achievement_id } } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + before_all do + group.add_maintainer(maintainer) + group.add_owner(owner) + end + + context 'when the user does not have permission' do + let(:current_user) { maintainer } + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not delete any user achievements' do + expect { subject }.not_to change { Achievements::UserAchievement.count } + end + end + + context 'when the user has permission' do + let(:current_user) { owner } + + context 'when the params are invalid' do + let(:user_achievement) { nil } + + it 'returns the validation error' do + subject + + expect(graphql_errors.to_s).to include('invalid value for userAchievementId (Expected value to not be null)') + end + end + + context 'when the user_achievement_id is invalid' do + let(:user_achievement_id) { "gid://gitlab/Achievements::UserAchievement/#{non_existing_record_id}" } + + it 'returns the relevant error' do + subject + + expect(graphql_errors.to_s) + .to include("The resource that you are attempting to access does not exist or you don't have permission") + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(achievements: false) + end + + it 'returns the relevant error' do + subject + + expect(graphql_errors.to_s) + .to include("The resource that you are attempting to access does not exist or you don't have permission") + end + end + + context 'when everything is ok' do + it 'deletes an user achievement' do + expect { subject }.to change { Achievements::UserAchievement.count }.by(-1) + end + + it 'returns the deleted user achievement' do + subject + + expect(graphql_data_at(:user_achievements_delete, :user_achievement, :achievement, :id)) + .to eq(achievement.to_global_id.to_s) + end + end + end +end diff --git a/spec/services/achievements/destroy_user_achievement_service_spec.rb b/spec/services/achievements/destroy_user_achievement_service_spec.rb new file mode 100644 index 00000000000..c5ff43fa1b2 --- /dev/null +++ b/spec/services/achievements/destroy_user_achievement_service_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Achievements::DestroyUserAchievementService, feature_category: :user_profile do + describe '#execute' do + let_it_be(:maintainer) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:group) { create(:group) } + + let_it_be(:achievement) { create(:achievement, namespace: group) } + let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) } + + subject(:response) { described_class.new(current_user, user_achievement).execute } + + before_all do + group.add_maintainer(maintainer) + group.add_owner(owner) + end + + context 'when user does not have permission' do + let(:current_user) { maintainer } + + it 'returns an error' do + expect(response).to be_error + expect(response.message).to match_array( + ['You have insufficient permissions to delete this user achievement']) + end + end + + context 'when user has permission' do + let(:current_user) { owner } + + it 'deletes the achievement' do + expect(response).to be_success + expect(Achievements::UserAchievement.find_by(id: user_achievement.id)).to be_nil + end + end + end +end diff --git a/spec/services/database/mark_migration_service_spec.rb b/spec/services/database/mark_migration_service_spec.rb new file mode 100644 index 00000000000..5fd2268484e --- /dev/null +++ b/spec/services/database/mark_migration_service_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Database::MarkMigrationService, feature_category: :database do + let(:service) { described_class.new(connection: connection, version: version) } + let(:version) { 1 } + let(:connection) { ApplicationRecord.connection } + + let(:migrations) do + [ + instance_double( + ActiveRecord::MigrationProxy, + version: 1, + name: 'migration_pending', + filename: 'db/migrate/1_migration_pending.rb' + ) + ] + end + + before do + ctx = instance_double(ActiveRecord::MigrationContext, migrations: migrations) + allow(connection).to receive(:migration_context).and_return(ctx) + end + + describe '#execute' do + subject(:execute) { service.execute } + + it 'marks the migration as successful' do + expect { execute } + .to change { ActiveRecord::SchemaMigration.where(version: version).count } + .by(1) + + is_expected.to be_success + end + + context 'when the migration does not exist' do + let(:version) { 123 } + + it { is_expected.to be_error } + it { expect(execute.reason).to eq(:not_found) } + + it 'does not insert records' do + expect { execute } + .not_to change { ActiveRecord::SchemaMigration.where(version: version).count } + end + end + + context 'when the migration was already executed' do + before do + allow(service).to receive(:all_versions).and_return([version]) + end + + it { is_expected.to be_error } + it { expect(execute.reason).to eq(:invalid) } + + it 'does not insert records' do + expect { execute } + .not_to change { ActiveRecord::SchemaMigration.where(version: version).count } + end + end + + context 'when the insert fails' do + it 'returns an error response' do + expect(service).to receive(:create_version).with(version).and_return(false) + + is_expected.to be_error + end + end + end +end diff --git a/spec/services/personal_access_tokens/last_used_service_spec.rb b/spec/services/personal_access_tokens/last_used_service_spec.rb index 20eabc20338..77ea5e10379 100644 --- a/spec/services/personal_access_tokens/last_used_service_spec.rb +++ b/spec/services/personal_access_tokens/last_used_service_spec.rb @@ -6,8 +6,8 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_ describe '#execute' do subject { described_class.new(personal_access_token).execute } - context 'when the personal access token has not been used recently' do - let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.year.ago) } + context 'when the personal access token was used 10 minutes ago', :freeze_time do + let(:personal_access_token) { create(:personal_access_token, last_used_at: 10.minutes.ago) } it 'updates the last_used_at timestamp' do expect { subject }.to change { personal_access_token.last_used_at } @@ -20,8 +20,8 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_ end end - context 'when the personal access token has been used recently' do - let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.minute.ago) } + context 'when the personal access token was used less than 10 minutes ago', :freeze_time do + let(:personal_access_token) { create(:personal_access_token, last_used_at: (10.minutes - 1.second).ago) } it 'does not update the last_used_at timestamp' do expect { subject }.not_to change { personal_access_token.last_used_at } @@ -43,5 +43,49 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_ expect(subject).to be_nil end end + + context 'when update_personal_access_token_usage_information_every_10_minutes is disabled' do + before do + stub_feature_flags(update_personal_access_token_usage_information_every_10_minutes: false) + end + + context 'when the personal access token was used 1 day ago', :freeze_time do + let(:personal_access_token) { create(:personal_access_token, last_used_at: 1.day.ago) } + + it 'updates the last_used_at timestamp' do + expect { subject }.to change { personal_access_token.last_used_at } + end + + it 'does not run on read-only GitLab instances' do + allow(::Gitlab::Database).to receive(:read_only?).and_return(true) + + expect { subject }.not_to change { personal_access_token.last_used_at } + end + end + + context 'when the personal access token was used less than 1 day ago', :freeze_time do + let(:personal_access_token) { create(:personal_access_token, last_used_at: (1.day - 1.second).ago) } + + it 'does not update the last_used_at timestamp' do + expect { subject }.not_to change { personal_access_token.last_used_at } + end + end + + context 'when the last_used_at timestamp is nil' do + let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: nil) } + + it 'updates the last_used_at timestamp' do + expect { subject }.to change { personal_access_token.last_used_at } + end + end + + context 'when not a personal access token' do + let_it_be(:personal_access_token) { create(:oauth_access_token) } + + it 'does not execute' do + expect(subject).to be_nil + end + end + end end end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 354d175d825..b3b6854481f 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -8228,7 +8228,6 @@ - './spec/requests/api/doorkeeper_access_spec.rb' - './spec/requests/api/environments_spec.rb' - './spec/requests/api/error_tracking/client_keys_spec.rb' -- './spec/requests/api/error_tracking/collector_spec.rb' - './spec/requests/api/events_spec.rb' - './spec/requests/api/feature_flags_spec.rb' - './spec/requests/api/feature_flags_user_lists_spec.rb' |