diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 09:10:52 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-09-20 09:10:52 +0000 |
commit | a7b422860c90eecd1b98845d234a8347686fbdcf (patch) | |
tree | f4a63124ef328ce146a82453b091e10f8189da36 /spec | |
parent | b2362c5f21e373820afc7d7a01ed104eaedd0e49 (diff) | |
download | gitlab-ce-a7b422860c90eecd1b98845d234a8347686fbdcf.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
13 files changed, 468 insertions, 62 deletions
diff --git a/spec/finders/personal_access_tokens_finder_spec.rb b/spec/finders/personal_access_tokens_finder_spec.rb index f22bff62082..21380cb6632 100644 --- a/spec/finders/personal_access_tokens_finder_spec.rb +++ b/spec/finders/personal_access_tokens_finder_spec.rb @@ -7,6 +7,50 @@ RSpec.describe PersonalAccessTokensFinder do described_class.new(options, current_user) end + describe '# searches PATs' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:time_token) do + create(:personal_access_token, created_at: DateTime.new(2022, 01, 02), + last_used_at: DateTime.new(2022, 01, 02)) + end + + let_it_be(:name_token) { create(:personal_access_token, name: 'test_1') } + + let_it_be(:impersonated_token) do + create(:personal_access_token, :impersonation, + created_at: DateTime.new(2022, 01, 02), + last_used_at: DateTime.new(2022, 01, 02), + name: 'imp_token' + ) + end + + shared_examples 'finding tokens by user and options' do + subject { finder(option, user).execute } + + it 'finds exactly' do + subject + + is_expected.to contain_exactly(*result) + end + end + + context 'by' do + where(:option, :user, :result) do + { created_before: DateTime.new(2022, 01, 03) } | create(:admin) | lazy { [time_token, impersonated_token] } + { created_after: DateTime.new(2022, 01, 01) } | create(:admin) | lazy { [time_token, name_token, impersonated_token] } + { last_used_before: DateTime.new(2022, 01, 03) } | create(:admin) | lazy { [time_token, impersonated_token] } + { last_used_before: DateTime.new(2022, 01, 03) } | create(:admin) | lazy { [time_token, impersonated_token] } + { impersonation: true } | create(:admin) | lazy { [impersonated_token] } + { search: 'test' } | create(:admin) | lazy { [name_token] } + end + + with_them do + it_behaves_like 'finding tokens by user and options' + end + end + end + describe '#execute' do let(:user) { create(:user) } let(:params) { {} } diff --git a/spec/frontend/__mocks__/monaco-editor/index.js b/spec/frontend/__mocks__/monaco-editor/index.js index 384f9993150..d09672a4ecf 100644 --- a/spec/frontend/__mocks__/monaco-editor/index.js +++ b/spec/frontend/__mocks__/monaco-editor/index.js @@ -8,10 +8,8 @@ import 'monaco-editor/esm/vs/language/css/monaco.contribution'; import 'monaco-editor/esm/vs/language/json/monaco.contribution'; import 'monaco-editor/esm/vs/language/html/monaco.contribution'; import 'monaco-editor/esm/vs/basic-languages/monaco.contribution'; -import 'monaco-yaml/lib/esm/monaco.contribution'; // This language starts trying to spin up web workers which obviously breaks in Jest environment jest.mock('monaco-editor/esm/vs/language/typescript/tsMode'); -jest.mock('monaco-yaml/lib/esm/yamlMode'); export * from 'monaco-editor/esm/vs/editor/editor.api'; diff --git a/spec/frontend/__mocks__/monaco-yaml/index.js b/spec/frontend/__mocks__/monaco-yaml/index.js new file mode 100644 index 00000000000..36681854d0b --- /dev/null +++ b/spec/frontend/__mocks__/monaco-yaml/index.js @@ -0,0 +1,4 @@ +const setDiagnosticsOptions = jest.fn(); +const yamlDefaults = {}; + +export { setDiagnosticsOptions, yamlDefaults }; diff --git a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js index 9a14e1a55eb..21f8979f1a9 100644 --- a/spec/frontend/editor/source_editor_ci_schema_ext_spec.js +++ b/spec/frontend/editor/source_editor_ci_schema_ext_spec.js @@ -1,4 +1,4 @@ -import { languages } from 'monaco-editor'; +import { setDiagnosticsOptions } from 'monaco-yaml'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import { CiSchemaExtension } from '~/editor/extensions/source_editor_ci_schema_ext'; @@ -52,16 +52,12 @@ describe('~/editor/editor_ci_config_ext', () => { }); describe('registerCiSchema', () => { - beforeEach(() => { - jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions'); - }); - describe('register validations options with monaco for yaml language', () => { const mockProjectNamespace = 'namespace1'; const mockProjectPath = 'project1'; const getConfiguredYmlSchema = () => { - return languages.yaml.yamlDefaults.setDiagnosticsOptions.mock.calls[0][0].schemas[0]; + return setDiagnosticsOptions.mock.calls[0][0].schemas[0]; }; it('with expected basic validation configuration', () => { @@ -77,8 +73,8 @@ describe('~/editor/editor_ci_config_ext', () => { completion: true, }; - expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledTimes(1); - expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( + expect(setDiagnosticsOptions).toHaveBeenCalledTimes(1); + expect(setDiagnosticsOptions).toHaveBeenCalledWith( expect.objectContaining(expectedOptions), ); }); diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js index fd9d481251d..4efc0ac6028 100644 --- a/spec/frontend/ide/utils_spec.js +++ b/spec/frontend/ide/utils_spec.js @@ -1,4 +1,5 @@ import { languages } from 'monaco-editor'; +import { setDiagnosticsOptions as yamlDiagnosticsOptions } from 'monaco-yaml'; import { isTextFile, registerLanguages, @@ -203,7 +204,6 @@ describe('WebIDE utils', () => { }; jest.spyOn(languages.json.jsonDefaults, 'setDiagnosticsOptions'); - jest.spyOn(languages.yaml.yamlDefaults, 'setDiagnosticsOptions'); }); it('registers the given schemas with monaco for both json and yaml languages', () => { @@ -212,7 +212,7 @@ describe('WebIDE utils', () => { expect(languages.json.jsonDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( expect.objectContaining({ schemas: [schema] }), ); - expect(languages.yaml.yamlDefaults.setDiagnosticsOptions).toHaveBeenCalledWith( + expect(yamlDiagnosticsOptions).toHaveBeenCalledWith( expect.objectContaining({ schemas: [schema] }), ); }); diff --git a/spec/frontend/projects/settings/branch_rules/branch_dropdown_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js index 79bce5a4b3f..11f219c1f90 100644 --- a/spec/frontend/projects/settings/branch_rules/branch_dropdown_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/edit/branch_dropdown_spec.js @@ -4,7 +4,7 @@ import { GlDropdown, GlSearchBoxByType, GlDropdownItem, GlSprintf } from '@gitla import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import BranchDropdown, { i18n, -} from '~/projects/settings/branch_rules/components/branch_dropdown.vue'; +} from '~/projects/settings/branch_rules/components/edit/branch_dropdown.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; import branchesQuery from '~/projects/settings/branch_rules/queries/branches.query.graphql'; import waitForPromises from 'helpers/wait_for_promises'; diff --git a/spec/frontend/projects/settings/branch_rules/rule_edit_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js index b0b2b9191d4..21e63fdb24d 100644 --- a/spec/frontend/projects/settings/branch_rules/rule_edit_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/edit/index_spec.js @@ -1,9 +1,9 @@ import { nextTick } from 'vue'; import { getParameterByName } from '~/lib/utils/url_utility'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import RuleEdit from '~/projects/settings/branch_rules/components/rule_edit.vue'; -import BranchDropdown from '~/projects/settings/branch_rules/components/branch_dropdown.vue'; -import Protections from '~/projects/settings/branch_rules/components/protections/index.vue'; +import RuleEdit from '~/projects/settings/branch_rules/components/edit/index.vue'; +import BranchDropdown from '~/projects/settings/branch_rules/components/edit/branch_dropdown.vue'; +import Protections from '~/projects/settings/branch_rules/components/edit/protections/index.vue'; jest.mock('~/lib/utils/url_utility', () => ({ getParameterByName: jest.fn().mockImplementation(() => 'main'), diff --git a/spec/frontend/projects/settings/branch_rules/components/protections/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js index 3592fa50622..ee90ff8318f 100644 --- a/spec/frontend/projects/settings/branch_rules/components/protections/index_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/edit/protections/index_spec.js @@ -3,10 +3,10 @@ import { GlLink } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import Protections, { i18n, -} from '~/projects/settings/branch_rules/components/protections/index.vue'; -import PushProtections from '~/projects/settings/branch_rules/components/protections/push_protections.vue'; -import MergeProtections from '~/projects/settings/branch_rules/components/protections/merge_protections.vue'; -import { protections } from '../../mock_data'; +} from '~/projects/settings/branch_rules/components/edit/protections/index.vue'; +import PushProtections from '~/projects/settings/branch_rules/components/edit/protections/push_protections.vue'; +import MergeProtections from '~/projects/settings/branch_rules/components/edit/protections/merge_protections.vue'; +import { protections } from '../../../mock_data'; describe('Branch Protections', () => { let wrapper; diff --git a/spec/frontend/projects/settings/branch_rules/components/protections/merge_protections_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js index 0e168a2ad78..b5fdc46d600 100644 --- a/spec/frontend/projects/settings/branch_rules/components/protections/merge_protections_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/edit/protections/merge_protections_spec.js @@ -2,8 +2,8 @@ import { GlFormGroup, GlFormCheckbox } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import MergeProtections, { i18n, -} from '~/projects/settings/branch_rules/components/protections/merge_protections.vue'; -import { membersAllowedToMerge, requireCodeOwnersApproval } from '../../mock_data'; +} from '~/projects/settings/branch_rules/components/edit/protections/merge_protections.vue'; +import { membersAllowedToMerge, requireCodeOwnersApproval } from '../../../mock_data'; describe('Merge Protections', () => { let wrapper; diff --git a/spec/frontend/projects/settings/branch_rules/components/protections/push_protections_spec.js b/spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js index d54dad08338..60bb7a51dcb 100644 --- a/spec/frontend/projects/settings/branch_rules/components/protections/push_protections_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/edit/protections/push_protections_spec.js @@ -2,8 +2,8 @@ import { GlFormGroup, GlSprintf, GlFormCheckbox } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import PushProtections, { i18n, -} from '~/projects/settings/branch_rules/components/protections/push_protections.vue'; -import { membersAllowedToPush, allowForcePush } from '../../mock_data'; +} from '~/projects/settings/branch_rules/components/edit/protections/push_protections.vue'; +import { membersAllowedToPush, allowForcePush } from '../../../mock_data'; describe('Push Protections', () => { let wrapper; diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb index 5bce6a2cc3f..67e7d444d25 100644 --- a/spec/models/personal_access_token_spec.rb +++ b/spec/models/personal_access_token_spec.rb @@ -105,6 +105,31 @@ RSpec.describe PersonalAccessToken do end end + describe '.last_used_before' do + context 'last_used_*' do + let_it_be(:date) { DateTime.new(2022, 01, 01) } + let_it_be(:token) { create(:personal_access_token, last_used_at: date ) } + # This token should never occur in the following tests and indicates that filtering was done correctly with it + let_it_be(:never_used_token) { create(:personal_access_token) } + + describe '.last_used_before' do + it 'returns personal access tokens used before the specified date only' do + expect(described_class.last_used_before(date + 1)).to contain_exactly(token) + end + end + + it 'does not return token that is last_used_at after given date' do + expect(described_class.last_used_before(date + 1)).not_to contain_exactly(never_used_token) + end + + describe '.last_used_after' do + it 'returns personal access tokens used after the specified date only' do + expect(described_class.last_used_after(date - 1)).to contain_exactly(token) + end + end + end + end + describe '.last_used_before_or_unused' do let(:last_used_at) { 1.month.ago.beginning_of_hour } let!(:unused_token) { create(:personal_access_token) } diff --git a/spec/models/preloaders/project_root_ancestor_preloader_spec.rb b/spec/models/preloaders/project_root_ancestor_preloader_spec.rb index 30036a6a033..bb0de24abe5 100644 --- a/spec/models/preloaders/project_root_ancestor_preloader_spec.rb +++ b/spec/models/preloaders/project_root_ancestor_preloader_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Preloaders::ProjectRootAncestorPreloader do let_it_be(:root_parent1) { create(:group, :private, name: 'root-1', path: 'root-1') } - let_it_be(:root_parent2) { create(:group, :private, name: 'root-2', path: 'root-2') } + let_it_be(:root_parent2) { create(:group, name: 'root-2', path: 'root-2') } let_it_be(:guest_project) { create(:project, name: 'public guest', path: 'public-guest') } let_it_be(:private_maintainer_project) do create(:project, :private, name: 'b private maintainer', path: 'b-private-maintainer', namespace: root_parent1) @@ -15,7 +15,7 @@ RSpec.describe Preloaders::ProjectRootAncestorPreloader do end let_it_be(:public_maintainer_project) do - create(:project, :private, name: 'a public maintainer', path: 'a-public-maintainer', namespace: root_parent2) + create(:project, name: 'a public maintainer', path: 'a-public-maintainer', namespace: root_parent2) end let(:root_query_regex) { /\ASELECT.+FROM "namespaces" WHERE "namespaces"."id" = \d+/ } @@ -36,20 +36,20 @@ RSpec.describe Preloaders::ProjectRootAncestorPreloader do it 'strong_memoizes the correct root_ancestor' do pristine_projects.each do |project| - expected_parent_id = project.root_ancestor&.id + preloaded_parent_id = project.root_ancestor&.id - expect(project.parent_id).to eq(expected_parent_id) + expect(preloaded_parent_id).to eq(project.parent_id) end end end context 'when use_traversal_ids FF is enabled' do context 'when the preloader is used' do - before do - preload_ancestors - end - context 'when no additional preloads are provided' do + before do + preload_ancestors(:group) + end + it_behaves_like 'executes N matching DB queries', 0 end @@ -57,6 +57,10 @@ RSpec.describe Preloaders::ProjectRootAncestorPreloader do let(:additional_preloads) { [:route] } let(:root_query_regex) { /\ASELECT.+FROM "routes" WHERE "routes"."source_id" = \d+/ } + before do + preload_ancestors + end + it_behaves_like 'executes N matching DB queries', 0, :full_path end end @@ -64,6 +68,17 @@ RSpec.describe Preloaders::ProjectRootAncestorPreloader do context 'when the preloader is not used' do it_behaves_like 'executes N matching DB queries', 4 end + + context 'when using a :group sti name and passing projects in a user namespace' do + let(:projects) { [private_developer_project] } + let(:additional_preloads) { [:ip_restrictions, :saml_provider] } + + it 'does not load a nil value for root_ancestor' do + preload_ancestors(:group) + + expect(pristine_projects.first.root_ancestor).to eq(private_developer_project.root_ancestor) + end + end end context 'when use_traversal_ids FF is disabled' do @@ -91,9 +106,22 @@ RSpec.describe Preloaders::ProjectRootAncestorPreloader do context 'when the preloader is not used' do it_behaves_like 'executes N matching DB queries', 4 end + + context 'when using a :group sti name and passing projects in a user namespace' do + let(:projects) { [private_developer_project] } + let(:additional_preloads) { [:ip_restrictions, :saml_provider] } + + it 'does not load a nil value for root_ancestor' do + preload_ancestors(:group) + + expect(pristine_projects.first.root_ancestor).to eq(private_developer_project.root_ancestor) + end + end end - def preload_ancestors - described_class.new(pristine_projects, :namespace, additional_preloads).execute + private + + def preload_ancestors(namespace_sti_name = :namespace) + described_class.new(pristine_projects, namespace_sti_name, additional_preloads).execute end end diff --git a/spec/requests/api/personal_access_tokens_spec.rb b/spec/requests/api/personal_access_tokens_spec.rb index 37b5a594f2a..31c4e8803e3 100644 --- a/spec/requests/api/personal_access_tokens_spec.rb +++ b/spec/requests/api/personal_access_tokens_spec.rb @@ -4,14 +4,34 @@ require 'spec_helper' RSpec.describe API::PersonalAccessTokens do let_it_be(:path) { '/personal_access_tokens' } - let_it_be(:token1) { create(:personal_access_token) } - let_it_be(:token2) { create(:personal_access_token) } - let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: token1.user) } - let_it_be(:current_user) { create(:user) } describe 'GET /personal_access_tokens' do + using RSpec::Parameterized::TableSyntax + + def map_id(json_resonse) + json_response.map { |pat| pat['id'] } + end + + shared_examples 'response as expected' do |params| + subject { get api(path, personal_access_token: current_users_token), params: params } + + it "status, count and result as expected" do + subject + + if status == :bad_request + expect(json_response).to eq(result) + elsif status == :ok + expect(map_id(json_response)).to a_collection_containing_exactly(*result) + end + + expect(response).to have_gitlab_http_status(status) + expect(json_response.count).to eq(result_count) + end + end + context 'logged in as an Administrator' do let_it_be(:current_user) { create(:admin) } + let_it_be(:current_users_token) { create(:personal_access_token, user: current_user) } it 'returns all PATs by default' do get api(path, current_user) @@ -21,60 +41,348 @@ RSpec.describe API::PersonalAccessTokens do end context 'filtered with user_id parameter' do + let_it_be(:token) { create(:personal_access_token) } + let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: token.user) } + it 'returns only PATs belonging to that user' do - get api(path, current_user), params: { user_id: token1.user.id } + get api(path, current_user), params: { user_id: token.user.id } expect(response).to have_gitlab_http_status(:ok) expect(json_response.count).to eq(2) - expect(json_response.first['user_id']).to eq(token1.user.id) + expect(json_response.first['user_id']).to eq(token.user.id) expect(json_response.last['id']).to eq(token_impersonated.id) end end - context 'logged in as a non-Administrator' do - let_it_be(:current_user) { create(:user) } + context 'filter with revoked parameter' do + let_it_be(:revoked_token) { create(:personal_access_token, revoked: true) } + let_it_be(:not_revoked_token1) { create(:personal_access_token, revoked: false) } + let_it_be(:not_revoked_token2) { create(:personal_access_token, revoked: false) } + + where(:revoked, :status, :result_count, :result) do + true | :ok | 1 | lazy { [revoked_token.id] } + false | :ok | 3 | lazy { [not_revoked_token1.id, not_revoked_token2.id, current_users_token.id] } + 'asdf' | :bad_request | 1 | { "error" => "revoked is invalid" } + end + + with_them do + it_behaves_like 'response as expected', revoked: params[:revoked] + end + end + + context 'filter with active parameter' do + let_it_be(:inactive_token1) { create(:personal_access_token, revoked: true) } + let_it_be(:inactive_token2) { create(:personal_access_token, expires_at: Time.new(2022, 01, 01, 00, 00, 00)) } + let_it_be(:active_token) { create(:personal_access_token) } + + where(:state, :status, :result_count, :result) do + 'inactive' | :ok | 2 | lazy { [inactive_token1.id, inactive_token2.id] } + 'active' | :ok | 2 | lazy { [active_token.id, current_users_token.id] } + 'asdf' | :bad_request | 1 | { "error" => "state does not have a valid value" } + end + + with_them do + it_behaves_like 'response as expected', state: params[:state] + end + end + + context 'filter with created parameter' do + let_it_be(:token1) { create(:personal_access_token, created_at: DateTime.new(2022, 01, 01, 12, 30, 25) ) } + + context 'test created_before' do + where(:created_at, :status, :result_count, :result) do + '2022-01-02' | :ok | 1 | lazy { [token1.id] } + '2022-01-01' | :ok | 0 | lazy { [] } + '2022-01-01T12:30:24' | :ok | 0 | lazy { [] } + '2022-01-01T12:30:25' | :ok | 1 | lazy { [token1.id] } + '2022-01-01T:12:30:26' | :ok | 1 | lazy { [token1.id] } + 'asdf' | :bad_request | 1 | { "error" => "created_before is invalid" } + end + + with_them do + it_behaves_like 'response as expected', created_before: params[:created_at] + end + end + + context 'test created_after' do + where(:created_at, :status, :result_count, :result) do + '2022-01-03' | :ok | 1 | lazy { [current_users_token.id] } + '2022-01-01' | :ok | 2 | lazy { [token1.id, current_users_token.id] } + '2022-01-01T12:30:25' | :ok | 2 | lazy { [token1.id, current_users_token.id] } + '2022-01-01T12:30:26' | :ok | 1 | lazy { [current_users_token.id] } + (DateTime.now + 1).to_s | :ok | 0 | lazy { [] } + 'asdf' | :bad_request | 1 | { "error" => "created_after is invalid" } + end + + with_them do + it_behaves_like 'response as expected', created_after: params[:created_at] + end + end + end + + context 'filter with last_used parameter' do + let_it_be(:token1) { create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 01, 12, 30, 25) ) } + let_it_be(:never_used_token) { create(:personal_access_token) } + + context 'test last_used_before' do + where(:last_used_at, :status, :result_count, :result) do + '2022-01-02' | :ok | 1 | lazy { [token1.id] } + '2022-01-01' | :ok | 0 | lazy { [] } + '2022-01-01T12:30:24' | :ok | 0 | lazy { [] } + '2022-01-01T12:30:25' | :ok | 1 | lazy { [token1.id] } + '2022-01-01T12:30:26' | :ok | 1 | lazy { [token1.id] } + 'asdf' | :bad_request | 1 | { "error" => "last_used_before is invalid" } + end + + with_them do + it_behaves_like 'response as expected', last_used_before: params[:last_used_at] + end + end + + context 'test last_used_after' do + where(:last_used_at, :status, :result_count, :result) do + '2022-01-03' | :ok | 1 | lazy { [current_users_token.id] } + '2022-01-01' | :ok | 2 | lazy { [token1.id, current_users_token.id] } + '2022-01-01T12:30:26' | :ok | 1 | lazy { [current_users_token.id] } + '2022-01-01T12:30:25' | :ok | 2 | lazy { [token1.id, current_users_token.id] } + (DateTime.now + 1).to_s | :ok | 0 | lazy { [] } + 'asdf' | :bad_request | 1 | { "error" => "last_used_after is invalid" } + end + + with_them do + it_behaves_like 'response as expected', last_used_after: params[:last_used_at] + end + end + end + + context 'filter with search parameter' do + let_it_be(:token1) { create(:personal_access_token, name: 'test_1') } + let_it_be(:token2) { create(:personal_access_token, name: 'test_2') } + let_it_be(:token3) { create(:personal_access_token, name: '') } + + where(:pattern, :status, :result_count, :result) do + 'test' | :ok | 2 | lazy { [token1.id, token2.id] } + '' | :ok | 4 | lazy { [token1.id, token2.id, token3.id, current_users_token.id] } + 'test_1' | :ok | 1 | lazy { [token1.id] } + 'asdf' | :ok | 0 | lazy { [] } + end + + with_them do + it_behaves_like 'response as expected', search: params[:pattern] + end + end + + context 'filter created_before/created_after combined with last_used_before/last_used_after' do + let_it_be(:date) { DateTime.new(2022, 01, 02) } + let_it_be(:token1) { create(:personal_access_token, created_at: date, last_used_at: date) } + + where(:date_before, :date_after, :status, :result_count, :result) do + '2022-01-03' | '2022-01-01' | :ok | 1 | lazy { [token1.id] } + '2022-01-01' | '2022-01-03' | :ok | 0 | lazy { [] } + '2022-01-03' | nil | :ok | 1 | lazy { [token1.id] } + nil | '2022-01-01' | :ok | 2 | lazy { [token1.id, current_users_token.id] } + end + + with_them do + it_behaves_like 'response as expected', { created_before: params[:date_before], + created_after: params[:date_after] } + it_behaves_like 'response as expected', { last_used_before: params[:date_before], + last_used_after: params[:date_after] } + end + end + + context 'filter created_before and created_after combined is valid' do + let_it_be(:token1) { create(:personal_access_token, created_at: DateTime.new(2022, 01, 02)) } + + where(:created_before, :created_after, :status, :result) do + '2022-01-02' | '2022-01-02' | :ok | lazy { [token1.id] } + '2022-01-03' | '2022-01-01' | :ok | lazy { [token1.id] } + '2022-01-01' | '2022-01-03' | :ok | lazy { [] } + '2022-01-03' | nil | :ok | lazy { [token1.id] } + nil | '2022-01-01' | :ok | lazy { [token1.id] } + end + + with_them do + it "returns all valid tokens" do + get api(path, personal_access_token: current_users_token), + params: { created_before: created_before, created_after: created_after } + + expect(response).to have_gitlab_http_status(status) + + expect(json_response.map { |pat| pat['id'] } ).to include(*result) if status == :ok + end + end + end + + context 'filter last_used_before and last_used_after combined is valid' do + let_it_be(:token1) { create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 02) ) } + + where(:last_used_before, :last_used_after, :status, :result) do + '2022-01-02' | '2022-01-02' | :ok | lazy { [token1.id] } + '2022-01-03' | '2022-01-01' | :ok | lazy { [token1.id] } + '2022-01-01' | '2022-01-03' | :ok | lazy { [] } + '2022-01-03' | nil | :ok | lazy { [token1.id] } + nil | '2022-01-01' | :ok | lazy { [token1.id] } + end + + with_them do + it "returns all valid tokens" do + get api(path, personal_access_token: current_users_token), + params: { last_used_before: last_used_before, last_used_after: last_used_after } + + expect(response).to have_gitlab_http_status(status) + + expect(json_response.map { |pat| pat['id'] } ).to include(*result) if status == :ok + end + end + end + end + + context 'logged in as a non-Administrator' do + let_it_be(:current_user) { create(:user) } + let_it_be(:current_users_token) { create(:personal_access_token, user: current_user) } + + it 'returns all PATs belonging to the signed-in user' do + get api(path, personal_access_token: current_users_token) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.map { |r| r['id'] }.uniq).to contain_exactly(current_users_token.id) + expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id) + end + + context 'filtered with user_id parameter' do let_it_be(:user) { create(:user) } - let_it_be(:token) { create(:personal_access_token, user: current_user) } - let_it_be(:other_token) { create(:personal_access_token, user: user) } - let_it_be(:token_impersonated) { create(:personal_access_token, impersonation: true, user: current_user) } - it 'returns all PATs belonging to the signed-in user' do - get api(path, current_user, personal_access_token: token) + it 'returns PATs belonging to the specific user' do + get api(path, current_user, personal_access_token: current_users_token), params: { user_id: current_user.id } expect(response).to have_gitlab_http_status(:ok) expect(json_response.count).to eq(1) + expect(json_response.map { |r| r['id'] }.uniq).to contain_exactly(current_users_token.id) expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id) end - context 'filtered with user_id parameter' do - it 'returns PATs belonging to the specific user' do - get api(path, current_user, personal_access_token: token), params: { user_id: current_user.id } + it 'is unauthorized if filtered by a user other than current_user' do + get api(path, current_user, personal_access_token: current_users_token), params: { user_id: user.id } - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.count).to eq(1) - expect(json_response.map { |r| r['user_id'] }.uniq).to contain_exactly(current_user.id) - end + expect(response).to have_gitlab_http_status(:unauthorized) + end + end - it 'is unauthorized if filtered by a user other than current_user' do - get api(path, current_user, personal_access_token: token), params: { user_id: user.id } + context 'filter with revoked parameter' do + let_it_be(:users_revoked_token) { create(:personal_access_token, revoked: true, user: current_user) } + let_it_be(:not_revoked_token) { create(:personal_access_token, revoked: false) } + let_it_be(:oter_revoked_token) { create(:personal_access_token, revoked: true) } - expect(response).to have_gitlab_http_status(:unauthorized) - end + where(:revoked, :status, :result_count, :result) do + true | :ok | 1 | lazy { [users_revoked_token.id] } + false | :ok | 1 | lazy { [current_users_token.id] } + end + + with_them do + it_behaves_like 'response as expected', revoked: params[:revoked] end end - context 'not authenticated' do - it 'is forbidden' do - get api(path) + context 'filter with active parameter' do + let_it_be(:users_inactive_token) { create(:personal_access_token, revoked: true, user: current_user) } + let_it_be(:inactive_token) { create(:personal_access_token, expires_at: Time.new(2022, 01, 01, 00, 00, 00)) } + let_it_be(:other_active_token) { create(:personal_access_token) } - expect(response).to have_gitlab_http_status(:unauthorized) + where(:state, :status, :result_count, :result) do + 'inactive' | :ok | 1 | lazy { [users_inactive_token.id] } + 'active' | :ok | 1 | lazy { [current_users_token.id] } + end + + with_them do + it_behaves_like 'response as expected', state: params[:state] + end + end + + # The created_before filter has been extensively tested in the 'logged in as administrator' section. + # Here it is only tested whether PATs to which the user has no access right are excluded from the filter function. + context 'filter with created parameter' do + let_it_be(:token1) do + create(:personal_access_token, created_at: DateTime.new(2022, 01, 02, 12, 30, 25), user: current_user ) + end + + let_it_be(:token2) { create(:personal_access_token, created_at: DateTime.new(2022, 01, 02, 12, 30, 25)) } + let_it_be(:status) { :ok } + + context 'created_before' do + let_it_be(:result_count) { 1 } + let_it_be(:result) { [token1.id] } + + it_behaves_like 'response as expected', created_before: '2022-01-03' + end + + context 'created_after' do + let_it_be(:result_count) { 2 } + let_it_be(:result) { [token1.id, current_users_token.id] } + + it_behaves_like 'response as expected', created_after: '2022-01-01' + end + end + + # The last_used_before filter has been extensively tested in the 'logged in as administrator' section. + # Here it is only tested whether PATs to which the user has no access right are excluded from the filter function. + context 'filter with last_used' do + let_it_be(:token1) do + create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 01, 12, 30, 25), user: current_user) + end + + let_it_be(:token2) { create(:personal_access_token, last_used_at: DateTime.new(2022, 01, 01, 12, 30, 25) ) } + let_it_be(:never_used_token) { create(:personal_access_token) } + let_it_be(:status) { :ok } + + context 'last_used_before' do + let_it_be(:result_count) { 1 } + let_it_be(:result) { [token1.id] } + + it_behaves_like 'response as expected', last_used_before: '2022-01-02' + end + + context 'last_used_after' do + let_it_be(:result_count) { 2 } + let_it_be(:result) { [token1.id, current_users_token.id] } + + it_behaves_like 'response as expected', last_used_after: '2022-01-01' + end + end + + # The search filter has been extensively tested in the 'logged in as administrator' section. + # Here it is only tested whether PATs to which the user has no access right are excluded from the filter function. + context 'filter with search parameter' do + let_it_be(:token1) { create(:personal_access_token, name: 'test_1', user: current_user) } + let_it_be(:token2) { create(:personal_access_token, name: 'test_1') } + let_it_be(:token3) { create(:personal_access_token, name: '') } + + where(:pattern, :status, :result_count, :result) do + 'test' | :ok | 1 | lazy { [token1.id] } + '' | :ok | 2 | lazy { [token1.id, current_users_token.id] } + 'test_1' | :ok | 1 | lazy { [token1.id] } + end + + with_them do + it_behaves_like 'response as expected', search: params[:pattern] end end end + + context 'not authenticated' do + it 'is forbidden' do + get api(path) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end end describe 'GET /personal_access_tokens/:id' do + let_it_be(:current_user) { create(:user) } let_it_be(:user_token) { create(:personal_access_token, user: current_user) } + let_it_be(:token1) { create(:personal_access_token) } let_it_be(:user_read_only_token) { create(:personal_access_token, scopes: ['read_repository'], user: current_user) } let_it_be(:user_token_path) { "/personal_access_tokens/#{user_token.id}" } let_it_be(:invalid_path) { "/personal_access_tokens/#{non_existing_record_id}" } @@ -136,6 +444,9 @@ RSpec.describe API::PersonalAccessTokens do end describe 'DELETE /personal_access_tokens/:id' do + let_it_be(:current_user) { create(:user) } + let_it_be(:token1) { create(:personal_access_token) } + let(:path) { "/personal_access_tokens/#{token1.id}" } context 'when current_user is an administrator', :enable_admin_mode do |