diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/registrations_controller_spec.rb | 5 | ||||
-rw-r--r-- | spec/features/invites_spec.rb | 16 | ||||
-rw-r--r-- | spec/features/projects/snippets/create_snippet_spec.rb | 2 | ||||
-rw-r--r-- | spec/features/uploads/user_uploads_file_to_note_spec.rb | 2 | ||||
-rw-r--r-- | spec/features/users/signup_spec.rb | 20 | ||||
-rw-r--r-- | spec/frontend/integrations/edit/components/integration_form_spec.js | 59 | ||||
-rw-r--r-- | spec/frontend/integrations/edit/store/actions_spec.js | 27 | ||||
-rw-r--r-- | spec/frontend/integrations/edit/store/getters_spec.js | 32 | ||||
-rw-r--r-- | spec/frontend/integrations/edit/store/mutations_spec.js | 24 | ||||
-rw-r--r-- | spec/frontend/integrations/edit/store/state_spec.js | 1 | ||||
-rw-r--r-- | spec/frontend/lib/utils/text_utility_spec.js | 15 | ||||
-rw-r--r-- | spec/requests/api/invitations_spec.rb | 98 | ||||
-rw-r--r-- | spec/requests/api/settings_spec.rb | 2 |
13 files changed, 292 insertions, 11 deletions
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index fdcea33b0be..2fb17e56f37 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -7,6 +7,7 @@ RSpec.describe RegistrationsController do before do stub_feature_flags(invisible_captcha: false) + stub_application_setting(require_admin_approval_after_user_signup: false) end describe '#new' do @@ -76,10 +77,6 @@ RSpec.describe RegistrationsController do end context 'when the `require_admin_approval_after_user_signup` setting is turned off' do - before do - stub_application_setting(require_admin_approval_after_user_signup: false) - end - it 'signs up the user in `active` state' do subject created_user = User.find_by(email: 'new@user.com') diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index d1520a5a53a..2ceffa896eb 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -10,6 +10,7 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do let(:group_invite) { group.group_members.invite.last } before do + stub_application_setting(require_admin_approval_after_user_signup: false) project.add_maintainer(owner) group.add_owner(owner) group.add_developer('user@example.com', owner) @@ -97,6 +98,21 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures do click_link 'Register now' end + context 'with admin appoval required enabled' do + before do + stub_application_setting(require_admin_approval_after_user_signup: true) + end + + let(:send_email_confirmation) { true } + + it 'does not sign the user in' do + fill_in_sign_up_form(new_user) + + expect(current_path).to eq(new_user_session_path) + expect(page).to have_content('You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator') + end + end + context 'email confirmation disabled' do let(:send_email_confirmation) { false } diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 28fe0a0b7e1..0ed9e23c7f8 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -62,7 +62,7 @@ RSpec.describe 'Projects > Snippets > Create Snippet', :js do click_button('Create snippet') wait_for_requests - link = find('a.no-attachment-icon img[alt="banana_sample"]')['src'] + link = find('a.no-attachment-icon img.js-lazy-loaded[alt="banana_sample"]')['src'] expect(link).to match(%r{/#{Regexp.escape(project.full_path)}/uploads/\h{32}/banana_sample\.gif\z}) end diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 1eb3b016152..589cc9f9b02 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -78,7 +78,7 @@ RSpec.describe 'User uploads file to note' do click_button 'Comment' wait_for_requests - expect(find('a.no-attachment-icon img[alt="dk"]')['src']) + expect(find('a.no-attachment-icon img.js-lazy-loaded[alt="dk"]')['src']) .to match(%r{/#{project.full_path}/uploads/\h{32}/dk\.png$}) end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index bb2b465a69a..bfdd1e1bdb7 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -43,6 +43,10 @@ end RSpec.describe 'Signup' do include TermsHelper + before do + stub_application_setting(require_admin_approval_after_user_signup: false) + end + let(:new_user) { build_stubbed(:user) } def fill_in_signup_form @@ -228,6 +232,22 @@ RSpec.describe 'Signup' do expect(current_path).to eq users_sign_up_welcome_path end end + + context 'with required admin approval enabled' do + before do + stub_application_setting(require_admin_approval_after_user_signup: true) + end + + it 'creates the user but does not sign them in' do + visit new_user_registration_path + + fill_in_signup_form + + expect { click_button 'Register' }.to change { User.count }.by(1) + expect(current_path).to eq new_user_session_path + expect(page).to have_content("You have signed up successfully. However, we could not sign you in because your account is awaiting approval from your GitLab administrator") + end + end end context 'with errors' do diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 4dd38c959f8..97e77ac87ab 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -5,6 +5,7 @@ import IntegrationForm from '~/integrations/edit/components/integration_form.vue import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue'; +import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue'; import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; @@ -44,6 +45,8 @@ describe('IntegrationForm', () => { const findOverrideDropdown = () => wrapper.find(OverrideDropdown); const findActiveCheckbox = () => wrapper.find(ActiveCheckbox); const findConfirmationModal = () => wrapper.find(ConfirmationModal); + const findResetConfirmationModal = () => wrapper.find(ResetConfirmationModal); + const findResetButton = () => wrapper.find('[data-testid="reset-button"]'); const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields); const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields); const findTriggerFields = () => wrapper.find(TriggerFields); @@ -75,6 +78,29 @@ describe('IntegrationForm', () => { expect(findConfirmationModal().exists()).toBe(true); }); + + describe('resetPath is empty', () => { + it('does not render ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.INSTANCE, + }); + + expect(findResetButton().exists()).toBe(false); + expect(findResetConfirmationModal().exists()).toBe(false); + }); + }); + + describe('resetPath is present', () => { + it('renders ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.INSTANCE, + resetPath: 'resetPath', + }); + + expect(findResetButton().exists()).toBe(true); + expect(findResetConfirmationModal().exists()).toBe(true); + }); + }); }); describe('integrationLevel is group', () => { @@ -85,6 +111,29 @@ describe('IntegrationForm', () => { expect(findConfirmationModal().exists()).toBe(true); }); + + describe('resetPath is empty', () => { + it('does not render ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.GROUP, + }); + + expect(findResetButton().exists()).toBe(false); + expect(findResetConfirmationModal().exists()).toBe(false); + }); + }); + + describe('resetPath is present', () => { + it('renders ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.GROUP, + resetPath: 'resetPath', + }); + + expect(findResetButton().exists()).toBe(true); + expect(findResetConfirmationModal().exists()).toBe(true); + }); + }); }); describe('integrationLevel is project', () => { @@ -95,6 +144,16 @@ describe('IntegrationForm', () => { expect(findConfirmationModal().exists()).toBe(false); }); + + it('does not render ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: 'project', + resetPath: 'resetPath', + }); + + expect(findResetButton().exists()).toBe(false); + expect(findResetConfirmationModal().exists()).toBe(false); + }); }); describe('type is "slack"', () => { diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js index 5356c0a411b..5b5c8d6f76e 100644 --- a/spec/frontend/integrations/edit/store/actions_spec.js +++ b/spec/frontend/integrations/edit/store/actions_spec.js @@ -1,6 +1,11 @@ import testAction from 'helpers/vuex_action_helper'; import createState from '~/integrations/edit/store/state'; -import { setOverride } from '~/integrations/edit/store/actions'; +import { + setOverride, + setIsSaving, + setIsTesting, + setIsResetting, +} from '~/integrations/edit/store/actions'; import * as types from '~/integrations/edit/store/mutation_types'; describe('Integration form store actions', () => { @@ -15,4 +20,24 @@ describe('Integration form store actions', () => { return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]); }); }); + + describe('setIsSaving', () => { + it('should commit isSaving mutation', () => { + return testAction(setIsSaving, true, state, [{ type: types.SET_IS_SAVING, payload: true }]); + }); + }); + + describe('setIsTesting', () => { + it('should commit isTesting mutation', () => { + return testAction(setIsTesting, true, state, [{ type: types.SET_IS_TESTING, payload: true }]); + }); + }); + + describe('setIsResetting', () => { + it('should commit isResetting mutation', () => { + return testAction(setIsResetting, true, state, [ + { type: types.SET_IS_RESETTING, payload: true }, + ]); + }); + }); }); diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js index 3353e0c84cc..7d4532a1059 100644 --- a/spec/frontend/integrations/edit/store/getters_spec.js +++ b/spec/frontend/integrations/edit/store/getters_spec.js @@ -1,5 +1,12 @@ -import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters'; +import { + currentKey, + isInheriting, + isDisabled, + propsSource, +} from '~/integrations/edit/store/getters'; import createState from '~/integrations/edit/store/state'; +import mutations from '~/integrations/edit/store/mutations'; +import * as types from '~/integrations/edit/store/mutation_types'; import { mockIntegrationProps } from '../mock_data'; describe('Integration form store getters', () => { @@ -45,6 +52,29 @@ describe('Integration form store getters', () => { }); }); + describe('isDisabled', () => { + it.each` + isSaving | isTesting | isResetting | expected + ${false} | ${false} | ${false} | ${false} + ${true} | ${false} | ${false} | ${true} + ${false} | ${true} | ${false} | ${true} + ${false} | ${false} | ${true} | ${true} + ${false} | ${true} | ${true} | ${true} + ${true} | ${false} | ${true} | ${true} + ${true} | ${true} | ${false} | ${true} + ${true} | ${true} | ${true} | ${true} + `( + 'when isSaving = $isSaving, isTesting = $isTesting, isResetting = $isResetting then isDisabled = $expected', + ({ isSaving, isTesting, isResetting, expected }) => { + mutations[types.SET_IS_SAVING](state, isSaving); + mutations[types.SET_IS_TESTING](state, isTesting); + mutations[types.SET_IS_RESETTING](state, isResetting); + + expect(isDisabled(state)).toBe(expected); + }, + ); + }); + describe('propsSource', () => { beforeEach(() => { state.defaultState = defaultState; diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js index 4b733726d44..4707b4b3714 100644 --- a/spec/frontend/integrations/edit/store/mutations_spec.js +++ b/spec/frontend/integrations/edit/store/mutations_spec.js @@ -16,4 +16,28 @@ describe('Integration form store mutations', () => { expect(state.override).toBe(true); }); }); + + describe(`${types.SET_IS_SAVING}`, () => { + it('sets isSaving', () => { + mutations[types.SET_IS_SAVING](state, true); + + expect(state.isSaving).toBe(true); + }); + }); + + describe(`${types.SET_IS_TESTING}`, () => { + it('sets isTesting', () => { + mutations[types.SET_IS_TESTING](state, true); + + expect(state.isTesting).toBe(true); + }); + }); + + describe(`${types.SET_IS_RESETTING}`, () => { + it('sets isResetting', () => { + mutations[types.SET_IS_RESETTING](state, true); + + expect(state.isResetting).toBe(true); + }); + }); }); diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js index fc193850a94..4d0f4a1da71 100644 --- a/spec/frontend/integrations/edit/store/state_spec.js +++ b/spec/frontend/integrations/edit/store/state_spec.js @@ -7,6 +7,7 @@ describe('Integration form state factory', () => { customState: {}, isSaving: false, isTesting: false, + isResetting: false, override: false, }); }); diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 6fef5f6b63c..d7cedb939d2 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -325,4 +325,19 @@ describe('text_utility', () => { expect(textUtils.hasContent(txt)).toEqual(result); }); }); + + describe('isValidSha1Hash', () => { + const validSha1Hash = '92d10c15'; + const stringOver40 = new Array(42).join('a'); + + it.each` + hash | valid + ${validSha1Hash} | ${true} + ${'__characters'} | ${false} + ${'abc'} | ${false} + ${stringOver40} | ${false} + `(`returns $valid for $hash`, ({ hash, valid }) => { + expect(textUtils.isValidSha1Hash(hash)).toBe(valid); + }); + }); }); diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb index 9befd4f533a..75586970abb 100644 --- a/spec/requests/api/invitations_spec.rb +++ b/spec/requests/api/invitations_spec.rb @@ -7,7 +7,8 @@ RSpec.describe API::Invitations do let(:developer) { create(:user) } let(:access_requester) { create(:user) } let(:stranger) { create(:user) } - let(:email) { 'email@example.org' } + let(:email) { 'email1@example.com' } + let(:email2) { 'email2@example.com' } let(:project) do create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project| @@ -75,7 +76,7 @@ RSpec.describe API::Invitations do it 'invites a list of new email addresses' do expect do - email_list = 'email1@example.com,email2@example.com' + email_list = [email, email2].join(',') post api("/#{source_type.pluralize}/#{source.id}/invitations", maintainer), params: { email: email_list, access_level: Member::DEVELOPER } @@ -204,4 +205,97 @@ RSpec.describe API::Invitations do let(:source) { group } end end + + shared_examples 'GET /:source_type/:id/invitations' do |source_type| + context "with :source_type == #{source_type.pluralize}" do + it_behaves_like 'a 404 response when source is private' do + let(:route) { get invitations_url(source, stranger) } + end + + %i[maintainer developer access_requester stranger].each do |type| + context "when authenticated as a #{type}" do + it 'returns 200' do + user = public_send(type) + + get invitations_url(source, user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) + end + end + end + + it 'avoids N+1 queries' do + # Establish baseline + get invitations_url(source, maintainer) + + control = ActiveRecord::QueryRecorder.new do + get invitations_url(source, maintainer) + end + + invite_member_by_email(source, source_type, email, maintainer) + + expect do + get invitations_url(source, maintainer) + end.not_to exceed_query_limit(control) + end + + it 'does not find confirmed members' do + get invitations_url(source, developer) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) + expect(json_response.map { |u| u['id'] }).not_to match_array [maintainer.id, developer.id] + end + + it 'finds all members with no query string specified' do + invite_member_by_email(source, source_type, email, developer) + invite_member_by_email(source, source_type, email2, developer) + + get invitations_url(source, developer), params: { query: '' } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + + expect(json_response).to be_an Array + expect(json_response.count).to eq(2) + expect(json_response.map { |u| u['invite_email'] }).to match_array [email, email2] + end + + it 'finds the invitation by invite_email with query string' do + invite_member_by_email(source, source_type, email, developer) + invite_member_by_email(source, source_type, email2, developer) + + get invitations_url(source, developer), params: { query: email } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.count).to eq(1) + expect(json_response.first['invite_email']).to eq(email) + expect(json_response.first['created_by_name']).to eq(developer.name) + expect(json_response.first['user_name']).to eq(nil) + end + + def invite_member_by_email(source, source_type, email, created_by) + create(:"#{source_type}_member", invite_token: '123', invite_email: email, source: source, user: nil, created_by: created_by) + end + end + end + + describe 'GET /projects/:id/invitations' do + it_behaves_like 'GET /:source_type/:id/invitations', 'project' do + let(:source) { project } + end + end + + describe 'GET /groups/:id/invitations' do + it_behaves_like 'GET /:source_type/:id/invitations', 'group' do + let(:source) { group } + end + end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index e03c0d37f3d..da6eaf0ae23 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -41,7 +41,7 @@ RSpec.describe API::Settings, 'Settings' do expect(json_response['spam_check_endpoint_enabled']).to be_falsey expect(json_response['spam_check_endpoint_url']).to be_nil expect(json_response['wiki_page_max_content_bytes']).to be_a(Integer) - expect(json_response['require_admin_approval_after_user_signup']).to eq(false) + expect(json_response['require_admin_approval_after_user_signup']).to eq(true) end end |