diff --git a/ee/lib/api/internal/ai/x_ray/scan.rb b/ee/lib/api/internal/ai/x_ray/scan.rb index e9c7f0b26383f892d289a81aba1a99f680515c06..8ce125f451282639d3481a723e6e49adfdf2b022 100644 --- a/ee/lib/api/internal/ai/x_ray/scan.rb +++ b/ee/lib/api/internal/ai/x_ray/scan.rb @@ -23,29 +23,35 @@ class Scan < ::API::Base def x_ray_enabled_on_instance? return true if ::Gitlab.org_or_com? - - ::License.feature_available?(:code_suggestions) && + return false unless ::License.feature_available?(:code_suggestions) + + if ::CodeSuggestions::SelfManaged::SERVICE_START_DATE.past? + ::GitlabSubscriptions::AddOnPurchase + .for_code_suggestions + .any? + else # Before service start date + # TODO: Remove this else branch after the service start date ::Gitlab::CurrentSettings.instance_level_code_suggestions_enabled + end end def x_ray_available? - group = current_job.namespace - return false unless group.namespace_settings.code_suggestions? - if Gitlab.org_or_com? - code_suggestions_add_on?(group) + code_suggestions_add_on? else ai_gateway_token.present? end end - def code_suggestions_add_on?(namespace) - return true unless ::Feature.enabled?(:purchase_code_suggestions) - - ::GitlabSubscriptions::AddOnPurchase - .for_code_suggestions - .by_namespace_id(namespace.id) - .any? + def code_suggestions_add_on? + if ::Feature.enabled?(:purchase_code_suggestions) + ::GitlabSubscriptions::AddOnPurchase + .for_code_suggestions + .by_namespace_id(current_namespace.id) + .any? + else + current_namespace.namespace_settings.code_suggestions? + end end def model_gateway_headers(headers, gateway_token) @@ -64,10 +70,15 @@ def saas_headers return {} unless Gitlab.com? { - 'X-Gitlab-Saas-Namespace-Ids' => [current_job.namespace.id.to_s] + 'X-Gitlab-Saas-Namespace-Ids' => [current_namespace.id.to_s] } end + def current_namespace + current_job.namespace + end + strong_memoize_attr :current_namespace + def ai_gateway_token ::CloudConnector::AccessService.new.access_token([:code_suggestions], gitlab_realm) end diff --git a/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb b/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb index e7fe1e37b04e1d1404c67de9b21f82455a14cc65..50b8a7163912b3d11d34bc9358334008271635e4 100644 --- a/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb +++ b/ee/spec/requests/api/internal/ai/x_ray/scan_spec.rb @@ -4,8 +4,7 @@ RSpec.describe API::Internal::Ai::XRay::Scan, feature_category: :code_suggestions do describe 'POST /internal/jobs/:id/x_ray/scan' do - let_it_be(:add_on_purchase) { create(:gitlab_subscription_add_on_purchase) } - let_it_be(:namespace) { add_on_purchase.namespace } + let_it_be(:namespace) { create(:group) } let_it_be(:job) { create(:ci_build, :running, namespace: namespace) } let(:ai_gateway_token) { 'ai gateway token' } @@ -93,45 +92,106 @@ stub_licensed_features(code_suggestions: true) end - context 'with code suggestions disabled on instance level' do - before do - stub_ee_application_setting(instance_level_code_suggestions_enabled: false) + # TODO: clean up date-related tests after the Code Suggestions service start date (16.9+) + context 'when before the service start date' do + around do |example| + travel_to(CodeSuggestions::SelfManaged::SERVICE_START_DATE - 1.day) do + example.run + end end - it 'returns NOT_FOUND status' do - post_api + context 'with code suggestions disabled on instance level' do + before do + stub_ee_application_setting(instance_level_code_suggestions_enabled: false) + end - expect(response).to have_gitlab_http_status(:not_found) - end - end + it 'returns NOT_FOUND status' do + post_api - context 'with code suggestions enabled on instance level' do - before do - stub_ee_application_setting(instance_level_code_suggestions_enabled: true) + expect(response).to have_gitlab_http_status(:not_found) + end end - context 'with code suggestions disabled on namespace level' do + context 'with code suggestions enabled on instance level' do before do - namespace.namespace_settings.update!(code_suggestions: false) + stub_ee_application_setting(instance_level_code_suggestions_enabled: true) + namespace.namespace_settings.update!(code_suggestions: true) end - it 'returns UNAUTHORIZED status' do + it 'checks ServiceAccessToken', :aggregate_failures do + token_double = instance_double(::CloudConnector::ServiceAccessToken) + expect(token_double).to receive(:token).and_return(ai_gateway_token) + expect(::CloudConnector::ServiceAccessToken).to receive_message_chain(:active, :last) + .and_return(token_double) + post_api + end - expect(response).to have_gitlab_http_status(:unauthorized) + context 'when ServiceAccessToken is missing' do + it 'returns UNAUTHORIZED status' do + post_api + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + context 'when instance has uuid available' do + let(:instance_uuid) { 'some uuid' } + + before do + allow(Gitlab::CurrentSettings).to receive(:uuid).and_return(instance_uuid) + token_double = instance_double(::CloudConnector::ServiceAccessToken, token: ai_gateway_token) + allow(::CloudConnector::ServiceAccessToken).to receive_message_chain(:active, :last) + .and_return(token_double) + end + + it_behaves_like 'successful send request via workhorse' + end + + context 'when instance has custom hostname' do + let(:hostname) { 'gitlab.local' } + + before do + stub_config(gitlab: { + protocol: 'http', + host: hostname, + url: "http://#{hostname}", + relative_url_root: "http://#{hostname}" + }) + + token_double = instance_double(::CloudConnector::ServiceAccessToken, token: ai_gateway_token) + allow(::CloudConnector::ServiceAccessToken).to receive_message_chain(:active, :last) + .and_return(token_double) + end + + it_behaves_like 'successful send request via workhorse' end end + end - context 'with code suggestions enabled on namespace level' do - before do - namespace.namespace_settings.update!(code_suggestions: true) + context 'when it is past the code suggestions service start date' do + around do |example| + travel_to(::CodeSuggestions::SelfManaged::SERVICE_START_DATE + 1.second) do + example.run end + end + + context 'with out add on' do + it 'returns NOT_FOUND status' do + post_api + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'with add on' do + before_all { create(:gitlab_subscription_add_on_purchase, namespace: namespace) } it 'checks ServiceAccessToken', :aggregate_failures do token_double = instance_double(::CloudConnector::ServiceAccessToken) expect(token_double).to receive(:token).and_return(ai_gateway_token) expect(::CloudConnector::ServiceAccessToken).to receive_message_chain(:active, :last) - .and_return(token_double) + .and_return(token_double) post_api end @@ -151,7 +211,7 @@ allow(Gitlab::CurrentSettings).to receive(:uuid).and_return(instance_uuid) token_double = instance_double(::CloudConnector::ServiceAccessToken, token: ai_gateway_token) allow(::CloudConnector::ServiceAccessToken).to receive_message_chain(:active, :last) - .and_return(token_double) + .and_return(token_double) end it_behaves_like 'successful send request via workhorse' @@ -170,7 +230,7 @@ token_double = instance_double(::CloudConnector::ServiceAccessToken, token: ai_gateway_token) allow(::CloudConnector::ServiceAccessToken).to receive_message_chain(:active, :last) - .and_return(token_double) + .and_return(token_double) end it_behaves_like 'successful send request via workhorse' @@ -181,6 +241,8 @@ end context 'when on SaaS instance', :saas do + before_all { create(:gitlab_subscription_add_on_purchase, namespace: namespace) } + let(:gitlab_realm) { "saas" } let(:namespace_workhorse_headers) do { @@ -188,25 +250,41 @@ } end - before do - stub_feature_flags(purchase_code_suggestions: true) - end - - context 'with code suggestions disabled on namespace level' do + context 'with purchase_code_suggestions feature disabled' do before do - namespace.namespace_settings.update!(code_suggestions: false) + stub_feature_flags(purchase_code_suggestions: false) end - it 'returns UNAUTHORIZED status' do - post_api + context 'with code suggestions enabled on namespace level' do + before do + allow_next_instance_of(Gitlab::CloudConnector::SelfIssuedToken) do |instance| + allow(instance).to receive(:encoded).and_return(ai_gateway_token) + end + end + + let(:namespace_workhorse_headers) do + { + "X-Gitlab-Saas-Namespace-Ids" => [namespace.id.to_s] + } + end - expect(response).to have_gitlab_http_status(:unauthorized) + it_behaves_like 'successful send request via workhorse' + end + + context 'with code suggestions disabled on namespace level' do + it 'returns UNAUTHORIZED status' do + namespace.namespace_settings.update!(code_suggestions: false) + + post_api + + expect(response).to have_gitlab_http_status(:unauthorized) + end end end - context 'with code suggestions enabled on namespace level' do + context 'with purchase_code_suggestions feature enabled' do before do - namespace.namespace_settings.update!(code_suggestions: true) + stub_feature_flags(purchase_code_suggestions: true) allow_next_instance_of(Gitlab::CloudConnector::SelfIssuedToken) do |instance| allow(instance).to receive(:encoded).and_return(ai_gateway_token) end @@ -232,20 +310,6 @@ expect(response).to have_gitlab_http_status(:unauthorized) end - context 'without purchase_code_suggestions feature' do - before do - stub_feature_flags(purchase_code_suggestions: false) - end - - let(:namespace_workhorse_headers) do - { - "X-Gitlab-Saas-Namespace-Ids" => [namespace_without_ai_access.id.to_s] - } - end - - it_behaves_like 'successful send request via workhorse' - end - context 'with personal namespace' do let(:user_namespace) { create(:user).namespace } let(:job_in_user_namespace) { create(:ci_build, :running, namespace: user_namespace) } @@ -269,21 +333,6 @@ expect(response).to have_gitlab_http_status(:unauthorized) end - - context 'without purchase_code_suggestions feature' do - before do - stub_feature_flags(purchase_code_suggestions: false) - user_namespace.namespace_settings.update!(code_suggestions: true) - end - - let(:namespace_workhorse_headers) do - { - "X-Gitlab-Saas-Namespace-Ids" => [user_namespace.id.to_s] - } - end - - it_behaves_like 'successful send request via workhorse' - end end end end