# frozen_string_literal: true require 'spec_helper' RSpec.describe TrialsController, :saas do let_it_be(:user) { create(:user, email_opted_in: true, last_name: 'Doe') } let(:dev_env_or_com) { true } let(:logged_in) { true } before do allow(::Gitlab).to receive(:dev_env_or_com?).and_return(dev_env_or_com) sign_in(user) if logged_in end shared_examples 'an authenticated endpoint' do let(:success_status) { :ok } context 'when not authenticated' do let(:logged_in) { false } it { is_expected.to redirect_to(new_trial_registration_url) } end context 'when authenticated' do it { is_expected.to have_gitlab_http_status(success_status) } end end shared_examples 'a dot-com only feature' do let(:success_status) { :ok } context 'when not on gitlab.com and not in development environment' do let(:dev_env_or_com) { false } it { is_expected.to have_gitlab_http_status(:not_found) } end context 'when on gitlab.com or in dev environment' do it { is_expected.to have_gitlab_http_status(success_status) } end end describe '#new' do subject(:get_new) do get :new response end context 'when the trial_registration_with_reassurance experiment is active', :experiment do before do stub_experiments(trial_registration_with_reassurance: :control) end it 'tracks a "render" event' do expect(experiment(:trial_registration_with_reassurance)).to track( :render, user: user, label: 'trials:new' ).with_context(actor: user).on_next_instance get_new end end it 'calls record_experiment_user for the experiments' do get_new end it_behaves_like 'an authenticated endpoint' it_behaves_like 'a dot-com only feature' end describe '#create_lead' do let(:post_params) { {} } let(:create_lead_result) { nil } before do allow_next_instance_of(GitlabSubscriptions::CreateLeadService) do |lead_service| expect(lead_service).to receive(:execute).and_return({ success: create_lead_result }) end end subject(:post_create_lead) do post :create_lead, params: post_params response end it_behaves_like 'an authenticated endpoint' it_behaves_like 'a dot-com only feature' context 'on success' do let(:create_lead_result) { true } it { is_expected.to redirect_to(select_trials_url) } context 'coming from about.gitlab.com' do let(:post_params) { { glm_source: 'about.gitlab.com' } } it 'redirects to trial onboarding' do is_expected.to redirect_to(new_users_sign_up_group_path(glm_source: 'about.gitlab.com', trial_onboarding_flow: true)) end end context 'when user has 1 trial eligible namespace', :experiment do let_it_be(:namespace) { create(:group, path: 'namespace-test') } let(:apply_trial_result) { true } before do namespace.add_owner(user) allow_next_instance_of(GitlabSubscriptions::ApplyTrialService) do |service| allow(service).to receive(:execute).and_return({ success: apply_trial_result }) end end context 'when the ApplyTrialService is successful' do it 'applies a trial to the namespace' do apply_trial_params = { uid: user.id, trial_user: ActionController::Parameters.new(post_params).permit(:namespace_id).merge(namespace_id: namespace.id, gitlab_com_trial: true, sync_to_gl: true) } expect_next_instance_of(GitlabSubscriptions::ApplyTrialService) do |service| expect(service).to receive(:execute).with(apply_trial_params).and_return({ success: true }) end post_create_lead end it 'tracks for the combined_registration experiment' do expect(experiment(:combined_registration)).to track(:create_trial).on_next_instance post_create_lead end context 'when the user is `setup_for_company: true`' do let(:user) { create(:user, setup_for_company: true) } context 'when there is a stored_location_for(:user) set' do let(:stored_location_for) { continuous_onboarding_getting_started_users_sign_up_welcome_path(project_id: 311) } before do controller.store_location_for(:user, stored_location_for) end context 'when the user is receiving the combined_registration candidate', :experiment do before do stub_experiments(combined_registration: :candidate) end it { is_expected.to redirect_to(stored_location_for) } end it { is_expected.to redirect_to(group_url(namespace, { trial: true })) } end it { is_expected.to redirect_to(group_url(namespace, { trial: true })) } end it { is_expected.to redirect_to(group_url(namespace, { trial: true })) } end context 'when the ApplyTrialService is unsuccessful' do let(:apply_trial_result) { false } it { is_expected.to render_template(:select) } end end end context 'on failure' do let(:create_lead_result) { false } it { is_expected.to render_template(:new) } end context 'request params to Lead Service' do let(:post_params) do { company_name: 'Gitlab', company_size: '1-99', first_name: user.first_name, last_name: user.last_name, phone_number: '1111111111', number_of_users: '20', country: 'IN', glm_content: 'free-billing', glm_source: 'about.gitlab.com' } end let(:extra_params) do { work_email: user.email, uid: user.id, setup_for_company: nil, skip_email_confirmation: true, gitlab_com_trial: true, provider: 'gitlab', newsletter_segment: user.email_opted_in } end let(:expected_params) do { company_name: 'Gitlab', company_size: '1-99', first_name: user.first_name, last_name: user.last_name, phone_number: '1111111111', number_of_users: '20', country: 'IN', glm_content: 'free-billing', glm_source: 'about.gitlab.com', work_email: user.email, uid: user.id, setup_for_company: nil, skip_email_confirmation: true, gitlab_com_trial: true, provider: 'gitlab', newsletter_segment: user.email_opted_in } end it 'sends appropriate request params' do expect_next_instance_of(GitlabSubscriptions::CreateLeadService) do |lead_service| expect(lead_service).to receive(:execute).with({ trial_user: ActionController::Parameters.new(expected_params).permit! }).and_return({ success: true }) end post_create_lead end end end describe '#create_hand_raise_lead' do let_it_be(:namespace) { create(:group) } let_it_be(:namespace_id) { namespace.id.to_s } let(:create_hand_raise_lead_result) { true } let(:hand_raise_lead_extra_params) do { work_email: user.email, uid: user.id, provider: 'gitlab', setup_for_company: user.setup_for_company, glm_content: 'group-billing', glm_source: 'gitlab.com' } end let(:post_params) do { namespace_id: namespace_id, first_name: 'James', last_name: 'Bond', company_name: 'ACME', company_size: '1-99', phone_number: '+1-192-10-10', country: 'US', state: 'CA', comment: 'I want to talk to sales.' } end subject do post :create_hand_raise_lead, params: post_params response end before_all do namespace.add_developer(user) end before do allow_next_instance_of(GitlabSubscriptions::CreateHandRaiseLeadService) do |service| allow(service).to receive(:execute).and_return(create_hand_raise_lead_result ? ServiceResponse.success : ServiceResponse.error(message: 'failed')) end end it_behaves_like 'a dot-com only feature' context 'when not authenticated' do let(:logged_in) { false } it { is_expected.to have_gitlab_http_status(:not_found) } end context 'when cannot find the namespace' do let(:namespace_id) { non_existing_record_id.to_s } it 'returns 404' do is_expected.to have_gitlab_http_status(:not_found) end end context 'when CreateHandRaiseLeadService fails' do let(:create_hand_raise_lead_result) { false } it 'returns 403' do is_expected.to have_gitlab_http_status(:forbidden) end end it 'calls the CreateHandRaiseLeadService with correct parameters' do expect_next_instance_of(GitlabSubscriptions::CreateHandRaiseLeadService) do |service| expected_params = ActionController::Parameters.new(post_params) .permit! .merge(hand_raise_lead_extra_params) expect(service).to receive(:execute).with(expected_params).and_return(ServiceResponse.success) end post :create_hand_raise_lead, params: post_params end end describe '#select' do subject(:get_select) do get :select response end it_behaves_like 'an authenticated endpoint' it_behaves_like 'a dot-com only feature' context 'when the trial_registration_with_reassurance experiment is active', :experiment do before do stub_experiments(trial_registration_with_reassurance: :control) end it 'tracks a "render" event' do expect(experiment(:trial_registration_with_reassurance)).to track( :render, user: user, label: 'trials:select' ).with_context(actor: user).on_next_instance get_select end end end describe '#apply' do let_it_be(:namespace) { create(:group, path: 'namespace-test') } let(:apply_trial_result) { nil } let(:post_params) { { namespace_id: namespace.id } } before do namespace.add_owner(user) allow_next_instance_of(GitlabSubscriptions::ApplyTrialService) do |service| allow(service).to receive(:execute).and_return({ success: apply_trial_result }) end allow(controller).to receive(:experiment).and_call_original end subject(:post_apply) do post :apply, params: post_params response end it_behaves_like 'an authenticated endpoint' it_behaves_like 'a dot-com only feature' context 'on success', :experiment do let(:apply_trial_result) { true } it { is_expected.to redirect_to("/#{namespace.path}?trial=true") } context 'when the trial_registration_with_reassurance experiment is active', :experiment do before do stub_experiments(trial_registration_with_reassurance: :control) end it 'tracks an "apply_trial" event' do expect(experiment(:trial_registration_with_reassurance)).to track( :apply_trial, user: user, namespace: namespace, label: 'trials:apply' ).with_context(actor: user).on_next_instance post_apply end end it 'calls tracking event for combined_registration experiment', :experiment do expect(experiment(:combined_registration)).to track(:create_trial).on_next_instance subject end context 'redirect trial user to feature' do using RSpec::Parameterized::TableSyntax where(:segment, :glm_content, :redirect) do :control | 'discover-group-security' | :group_url :candidate | 'discover-group-security' | :group_security_dashboard_url :control | 'discover-project-security' | :group_url :candidate | 'discover-project-security' | :group_security_dashboard_url end with_them do let(:post_params) { { namespace_id: namespace.id, glm_content: glm_content } } let(:variant) { segment == :control ? :control : :experimental } let(:redirect_url) do redirect == :group_url ? group_url(namespace, { trial: true }) : group_security_dashboard_url(namespace, { trial: true }) end before do stub_experiments(redirect_trial_user_to_feature: segment) end it { is_expected.to redirect_to(redirect_url) } it 'records the subject' do expect(Experiment).to receive(:add_subject).with('redirect_trial_user_to_feature', variant: variant, subject: namespace) post_apply end end end context 'with a new Group' do let(:post_params) { { new_group_name: 'GitLab' } } it 'creates the Group' do expect { post_apply }.to change { Group.count }.by(1) end end end context 'on failure' do let(:apply_trial_result) { false } it { is_expected.to render_template(:select) } it 'does not call the record conversion method for the experiments' do post_apply end context 'with a new Group' do let(:post_params) { { new_group_name: 'admin' } } it { is_expected.to render_template(:select) } it 'does not create the Group' do expect { post_apply }.not_to change { Group.count } end end end it 'calls the ApplyTrialService with correct parameters' do gl_com_params = { gitlab_com_trial: true, sync_to_gl: true } post_params = { namespace_id: namespace.id.to_s, trial_entity: 'company', glm_source: 'source', glm_content: 'content' } apply_trial_params = { uid: user.id, trial_user: ActionController::Parameters.new(post_params).permit(:namespace_id, :trial_entity, :glm_source, :glm_content).merge(gl_com_params) } expect_next_instance_of(GitlabSubscriptions::ApplyTrialService) do |service| expect(service).to receive(:execute).with(apply_trial_params).and_return({ success: true }) end post :apply, params: post_params end end describe '#extend_reactivate' do let!(:namespace) { create(:group_with_plan, trial_ends_on: Date.tomorrow, path: 'namespace-test') } let(:namespace_id) { namespace.id } let(:trial_extension_type) { GitlabSubscription.trial_extension_types[:extended].to_s } let(:put_params) { { namespace_id: namespace_id, trial_extension_type: trial_extension_type } } let(:extend_reactivate_trial_result) { true } let(:is_owner?) { true } before do if is_owner? namespace.add_owner(user) else namespace.add_developer(user) end allow_next_instance_of(GitlabSubscriptions::ExtendReactivateTrialService) do |service| allow(service).to receive(:execute).and_return(extend_reactivate_trial_result ? ServiceResponse.success : ServiceResponse.error(message: 'failed')) end end subject do put :extend_reactivate, params: put_params response end it_behaves_like 'an authenticated endpoint' it_behaves_like 'a dot-com only feature' context 'on success' do it { is_expected.to have_gitlab_http_status(:ok) } end context 'on failure' do context 'when user is not namespace owner' do let(:is_owner?) { false } it 'returns 403' do is_expected.to have_gitlab_http_status(:forbidden) end end context 'when cannot find the namespace' do let(:namespace_id) { non_existing_record_id.to_s } it 'returns 404' do is_expected.to have_gitlab_http_status(:not_found) end end context 'when trial extension type is neither EXTEND nor REACTIVATE' do let(:trial_extension_type) { nil } it 'returns 403' do is_expected.to have_gitlab_http_status(:forbidden) end end context 'when trial extension type is EXTEND' do let(:trial_extension_type) { GitlabSubscription.trial_extension_types[:extended].to_s } it 'returns 403 if the namespace cannot extend' do namespace.gitlab_subscription.update_column(:trial_extension_type, GitlabSubscription.trial_extension_types[:extended]) is_expected.to have_gitlab_http_status(:forbidden) end end context 'when trial extension type is REACTIVATE' do let(:trial_extension_type) { GitlabSubscription.trial_extension_types[:reactivated].to_s } it 'returns 403 if the namespace cannot reactivate' do namespace.gitlab_subscription.update_column(:trial_extension_type, GitlabSubscription.trial_extension_types[:extended]) is_expected.to have_gitlab_http_status(:forbidden) end end context 'when ExtendReactivateTrialService fails' do let(:extend_reactivate_trial_result) { false } it 'returns 403' do is_expected.to have_gitlab_http_status(:forbidden) end end end it 'calls the ExtendReactivateTrialService with correct parameters' do gl_com_params = { gitlab_com_trial: true } put_params = { namespace_id: namespace.id.to_s, trial_extension_type: GitlabSubscription.trial_extension_types[:extended].to_s, trial_entity: 'company', glm_source: 'source', glm_content: 'content' } extend_reactivate_trial_params = { uid: user.id, trial_user: ActionController::Parameters.new(put_params).permit(:namespace_id, :trial_extension_type, :trial_entity, :glm_source, :glm_content).merge(gl_com_params) } expect_next_instance_of(GitlabSubscriptions::ExtendReactivateTrialService) do |service| expect(service).to receive(:execute).with(extend_reactivate_trial_params).and_return(ServiceResponse.success) end put :extend_reactivate, params: put_params end end describe 'confirm email warning' do before do get :new end RSpec::Matchers.define :set_confirm_warning_for do |email| match do |response| expect(controller).to set_flash.now[:warning].to include("Please check your email (#{email}) to verify that you own this address and unlock the power of CI/CD.") end end context 'with an unconfirmed email address present' do let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'unconfirmed@gitlab.com') } before do sign_in(user) end it { is_expected.not_to set_confirm_warning_for(user.unconfirmed_email) } end context 'without an unconfirmed email address present' do let(:user) { create(:user, confirmed_at: nil) } it { is_expected.not_to set_confirm_warning_for(user.email) } end end describe '#skip' do subject(:get_skip) { get :skip } context 'when the user is `setup_for_company: true`' do let(:user) { create(:user, setup_for_company: true) } it { is_expected.to redirect_to(dashboard_projects_path) } context 'and has a stored_location_for set' do before do controller.store_location_for(:user, new_trial_path) end it { is_expected.to redirect_to(dashboard_projects_path) } context 'when the user is receiving the combined_registration candidate', :experiment do before do stub_experiments(combined_registration: :candidate) end it { is_expected.to redirect_to(new_trial_path) } end end end context 'when the user is `setup_for_company: false`' do it { is_expected.to redirect_to(dashboard_projects_path) } end end end