Commit 891ee89d authored by Sean McGivern's avatar Sean McGivern

Merge branch 'extract-gitlab-features-class' into 'master'

Extract GitlabSubscriptions::Features class

See merge request gitlab-org/gitlab!82129
parents 74b0b68e f3a8759a
......@@ -11,7 +11,6 @@ module Integrations
validates :api_token, presence: true, if: :activated?
validates :zentao_product_xid, presence: true, if: :activated?
# License Level: EEP_FEATURES
def self.issues_license_available?(project)
project&.licensed_feature_available?(:zentao_issues_integration)
end
......
......@@ -17,9 +17,8 @@ feature such as [Related issues](../user/project/issues/related_issues.md) or
[Service Desk](../user/project/service_desk.md),
it should be restricted on namespace scope.
1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES`, or `EEU_FEATURES` constants in
`ee/app/models/license.rb`. Note that the prefix `EES` signifies Starter, `EEP` signifies
Premium, and `EEU` signifies Ultimate.
1. Add the feature symbol on `STARTER_FEATURES`, `PREMIUM_FEATURES`, or `ULTIMATE_FEATURES` constants in
`ee/app/models/gitlab_subscriptions/features.rb`.
1. Check using:
```ruby
......@@ -33,8 +32,8 @@ However, for features such as [Geo](../administration/geo/index.md) and
to only a subset of projects or namespaces, the check is made directly in
the instance license.
1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES` or `EEU_FEATURES` constants in
`ee/app/models/license.rb`.
1. Add the feature symbol to `STARTER_FEATURES`, `PREMIUM_FEATURES` or `ULTIMATE_FEATURES` constants in
`ee/app/models/gitlab_subscriptions/features.rb`.
1. Add the same feature symbol to `GLOBAL_FEATURES`.
1. Check using:
......
......@@ -95,7 +95,7 @@ class SubscriptionsController < ApplicationController
return render json: group.errors.to_json
end
response = Subscriptions::CreateService.new(
response = GitlabSubscriptions::CreateService.new(
current_user,
group: group,
customer_params: customer_params,
......
......@@ -5,7 +5,7 @@ module Admin
include Gitlab::Utils::StrongMemoize
def send_emails_from_admin_area_feature_available?
License.feature_available?(:send_emails_from_admin_area) || License.features_with_usage_ping.include?(:send_emails_from_admin_area)
License.feature_available?(:send_emails_from_admin_area) || GitlabSubscriptions::Features.usage_ping_feature?(:send_emails_from_admin_area)
end
def admin_emails_are_currently_rate_limited?
......
......@@ -3,7 +3,7 @@
module Admin
module IpRestrictionHelper
def ip_restriction_feature_available?(group)
group.licensed_feature_available?(:group_ip_restriction) || License.features_with_usage_ping.include?(:group_ip_restriction)
group.licensed_feature_available?(:group_ip_restriction) || GitlabSubscriptions::Features.usage_ping_feature?(:group_ip_restriction)
end
end
end
......@@ -3,7 +3,7 @@
module Admin
module RepoSizeLimitHelper
def repo_size_limit_feature_available?
License.feature_available?(:repository_size_limit) || License.features_with_usage_ping.include?(:repository_size_limit)
License.feature_available?(:repository_size_limit) || GitlabSubscriptions::Features.usage_ping_feature?(:repository_size_limit)
end
end
end
......@@ -69,7 +69,7 @@ module EE
end
scope :with_feature_available_in_plan, -> (feature) do
plans = plans_with_feature(feature)
plans = GitlabSubscriptions::Features.saas_plans_with_feature(feature)
matcher = ::Plan.where(name: plans)
.joins(:hosted_subscriptions)
.where("gitlab_subscriptions.namespace_id = namespaces.id")
......@@ -140,14 +140,6 @@ module EE
limit.presence || build_namespace_limit
end
class_methods do
extend ::Gitlab::Utils::Override
def plans_with_feature(feature)
LICENSE_PLANS_TO_NAMESPACE_PLANS.values_at(*License.plans_with_feature(feature)).flatten
end
end
override :move_dir
def move_dir
succeeded = super
......@@ -186,7 +178,7 @@ module EE
def feature_available_in_plan?(feature)
available_features = strong_memoize(:features_available_in_plan) do
Hash.new do |h, f|
h[f] = (plans.map(&:name) & self.class.plans_with_feature(f)).any?
h[f] = (plans.map(&:name) & GitlabSubscriptions::Features.saas_plans_with_feature(f)).any?
end
end
......
......@@ -671,7 +671,7 @@ module EE
return super unless License.current
License.current.features.select do |feature|
License.global_feature?(feature) || licensed_feature_available?(feature)
GitlabSubscriptions::Features.global?(feature) || licensed_feature_available?(feature)
end
end
......
# frozen_string_literal: true
# All GitLab features that are available after purchasing a GitLab subscription
# either SaaS or self-managed.
# This class defines methods to check feature availability and their relation
# to GitLab plans.
module GitlabSubscriptions
class Features
# Global features that cannot be restricted to only a subset of projects or namespaces.
# Use `License.feature_available?(:feature)` to check if these features are available.
# For all other features, use `project.feature_available?` or `namespace.feature_available?` when possible.
GLOBAL_FEATURES = %i[
admin_audit_log
auditor_user
custom_file_templates
custom_project_templates
db_load_balancing
default_branch_protection_restriction_in_groups
elastic_search
enterprise_templates
extended_audit_events
external_authorization_service_api_management
geo
ldap_group_sync
ldap_group_sync_filter
multiple_ldap_servers
object_storage
pages_size_limit
project_aliases
repository_size_limit
required_ci_templates
seat_link
usage_quotas
].freeze
STARTER_FEATURES = %i[
audit_events
blocked_issues
board_iteration_lists
code_owners
code_review_analytics
contribution_analytics
description_diffs
elastic_search
full_codequality_report
group_activity_analytics
group_bulk_edit
group_webhooks
issuable_default_templates
issue_weights
iterations
ldap_group_sync
member_lock
merge_request_approvers
milestone_charts
multiple_issue_assignees
multiple_ldap_servers
multiple_merge_request_assignees
multiple_merge_request_reviewers
project_merge_request_analytics
protected_refs_for_users
push_rules
repository_mirrors
resource_access_token
seat_link
scoped_issue_board
usage_quotas
visual_review_app
wip_limits
].freeze
PREMIUM_FEATURES = %i[
adjourned_deletion_for_projects_and_groups
admin_audit_log
alert_metric_upload
auditor_user
blocking_merge_requests
board_assignee_lists
board_milestone_lists
ci_cd_projects
ci_secrets_management
cluster_agents_gitops
cluster_agents_ci_impersonation
cluster_deployments
code_owner_approval_required
commit_committer_check
compliance_framework
custom_compliance_frameworks
cross_project_pipelines
custom_file_templates
custom_file_templates_for_namespace
custom_project_templates
cycle_analytics_for_groups
cycle_analytics_for_projects
db_load_balancing
default_branch_protection_restriction_in_groups
default_project_deletion_protection
disable_name_update_for_users
email_additional_text
epics
extended_audit_events
external_authorization_service_api_management
feature_flags_related_issues
feature_flags_code_references
file_locks
geo
generic_alert_fingerprinting
git_two_factor_enforcement
github_project_service_integration
group_allowed_email_domains
group_coverage_reports
group_forking_protection
group_merge_request_analytics
group_milestone_project_releases
group_project_templates
group_repository_analytics
group_saml
group_saml_group_sync
group_scoped_ci_variables
group_wikis
incident_sla
incident_metric_upload
ide_schema_config
issues_analytics
jira_issues_integration
ldap_group_sync_filter
merge_pipelines
merge_request_performance_metrics
admin_merge_request_approvers_rules
merge_trains
metrics_reports
multiple_alert_http_integrations
multiple_approval_rules
multiple_group_issue_boards
multiple_iteration_cadences
object_storage
operations_dashboard
package_forwarding
pages_size_limit
productivity_analytics
project_aliases
protected_environments
reject_unsigned_commits
required_ci_templates
scoped_labels
smartcard_auth
swimlanes
type_of_work_analytics
minimal_access_role
unprotection_restrictions
ci_project_subscriptions
incident_timeline_view
oncall_schedules
escalation_policies
export_user_permissions
zentao_issues_integration
].freeze
ULTIMATE_FEATURES = %i[
api_fuzzing
auto_rollback
cilium_alerts
cluster_image_scanning
external_status_checks
container_scanning
coverage_fuzzing
credentials_inventory
dast
dependency_scanning
devops_adoption
dora4_analytics
enforce_personal_access_token_expiration
enforce_ssh_key_expiration
enterprise_templates
environment_alerts
evaluate_group_level_compliance_pipeline
external_audit_events
group_ci_cd_analytics
group_level_compliance_dashboard
group_level_devops_adoption
incident_management
incident_timeline_events
inline_codequality
insights
instance_level_devops_adoption
issuable_health_status
jira_vulnerabilities_integration
jira_issue_association_enforcement
kubernetes_cluster_vulnerabilities
license_scanning
personal_access_token_expiration_policy
project_quality_summary
prometheus_alerts
pseudonymizer
quality_management
related_epics
release_evidence_test_artifacts
report_approver_rules
requirements
sast
sast_iac
sast_custom_rulesets
sast_fp_reduction
secret_detection
security_configuration_in_ui
security_dashboard
security_on_demand_scans
security_orchestration_policies
ssh_key_expiration_policy
status_page
subepics
threat_monitoring
vulnerability_auto_fix
vulnerability_finding_signatures
].freeze
STARTER_FEATURES_WITH_USAGE_PING = %i[
send_emails_from_admin_area
repository_size_limit
].freeze
PREMIUM_FEATURES_WITH_USAGE_PING = %i[
group_ip_restriction
].freeze
ALL_STARTER_FEATURES = STARTER_FEATURES + STARTER_FEATURES_WITH_USAGE_PING
ALL_PREMIUM_FEATURES = ALL_STARTER_FEATURES + PREMIUM_FEATURES + PREMIUM_FEATURES_WITH_USAGE_PING
ALL_ULTIMATE_FEATURES = ALL_PREMIUM_FEATURES + ULTIMATE_FEATURES
ALL_FEATURES = ALL_ULTIMATE_FEATURES
FEATURES_WITH_USAGE_PING = STARTER_FEATURES_WITH_USAGE_PING + PREMIUM_FEATURES_WITH_USAGE_PING
FEATURES_BY_PLAN = {
License::STARTER_PLAN => ALL_STARTER_FEATURES,
License::PREMIUM_PLAN => ALL_PREMIUM_FEATURES,
License::ULTIMATE_PLAN => ALL_ULTIMATE_FEATURES
}.freeze
LICENSE_PLANS_TO_SAAS_PLANS = {
License::STARTER_PLAN => [::Plan::BRONZE],
License::PREMIUM_PLAN => [::Plan::SILVER, ::Plan::PREMIUM, ::Plan::PREMIUM_TRIAL],
License::ULTIMATE_PLAN => [::Plan::GOLD, ::Plan::ULTIMATE, ::Plan::ULTIMATE_TRIAL, ::Plan::OPEN_SOURCE]
}.freeze
PLANS_BY_FEATURE = FEATURES_BY_PLAN.each_with_object({}) do |(plan, features), hash|
features.each do |feature|
hash[feature] ||= []
hash[feature] << plan
end
end.freeze
# Add on codes that may occur in legacy licenses that don't have a plan yet.
FEATURES_FOR_ADD_ONS = {
'GitLab_Auditor_User' => :auditor_user,
'GitLab_FileLocks' => :file_locks,
'GitLab_Geo' => :geo
}.freeze
class << self
def features(plan:, add_ons:)
(for_plan(plan) + for_add_ons(add_ons)).to_set
end
def global?(feature)
GLOBAL_FEATURES.include?(feature)
end
def usage_ping_feature?(feature)
features_with_usage_ping.include?(feature)
end
def plans_with_feature(feature)
if global?(feature)
raise ArgumentError, "Use `License.feature_available?` for features that cannot be restricted to only a subset of projects or namespaces"
end
PLANS_BY_FEATURE.fetch(feature, [])
end
def saas_plans_with_feature(feature)
LICENSE_PLANS_TO_SAAS_PLANS.values_at(*plans_with_feature(feature)).flatten
end
private
def features_with_usage_ping
return FEATURES_WITH_USAGE_PING if Gitlab::CurrentSettings.usage_ping_features_enabled?
[]
end
def for_plan(plan)
FEATURES_BY_PLAN.fetch(plan, [])
end
def for_add_ons(add_ons)
add_ons.map { |name, count| FEATURES_FOR_ADD_ONS[name] if count.to_i > 0 }.compact
end
end
end
end
# frozen_string_literal: true
# License is the artifact of purchasing a GitLab subscription for self-managed
# and it is installed at instance level.
# GitLab SaaS is a special self-managed instance which has a license installed
# that is mapped to an Ultimate plan.
class License < ApplicationRecord
include ActionView::Helpers::NumberHelper
include Gitlab::Utils::StrongMemoize
......@@ -17,247 +21,6 @@ class License < ApplicationRecord
EE_ALL_PLANS = [STARTER_PLAN, PREMIUM_PLAN, ULTIMATE_PLAN].freeze
EES_FEATURES_WITH_USAGE_PING = %i[
send_emails_from_admin_area
repository_size_limit
].freeze
EEP_FEATURES_WITH_USAGE_PING = %i[
group_ip_restriction
].freeze
EES_FEATURES = %i[
audit_events
blocked_issues
board_iteration_lists
code_owners
code_review_analytics
contribution_analytics
description_diffs
elastic_search
full_codequality_report
group_activity_analytics
group_bulk_edit
group_webhooks
issuable_default_templates
issue_weights
iterations
ldap_group_sync
member_lock
merge_request_approvers
milestone_charts
multiple_issue_assignees
multiple_ldap_servers
multiple_merge_request_assignees
multiple_merge_request_reviewers
project_merge_request_analytics
protected_refs_for_users
push_rules
repository_mirrors
resource_access_token
seat_link
scoped_issue_board
usage_quotas
visual_review_app
wip_limits
].freeze + EES_FEATURES_WITH_USAGE_PING
EEP_FEATURES = EES_FEATURES + EEP_FEATURES_WITH_USAGE_PING + %i[
adjourned_deletion_for_projects_and_groups
admin_audit_log
alert_metric_upload
auditor_user
blocking_merge_requests
board_assignee_lists
board_milestone_lists
ci_cd_projects
ci_secrets_management
cluster_agents_gitops
cluster_agents_ci_impersonation
cluster_deployments
code_owner_approval_required
commit_committer_check
compliance_framework
custom_compliance_frameworks
cross_project_pipelines
custom_file_templates
custom_file_templates_for_namespace
custom_project_templates
cycle_analytics_for_groups
cycle_analytics_for_projects
db_load_balancing
default_branch_protection_restriction_in_groups
default_project_deletion_protection
disable_name_update_for_users
email_additional_text
epics
extended_audit_events
external_authorization_service_api_management
feature_flags_related_issues
feature_flags_code_references
file_locks
geo
generic_alert_fingerprinting
git_two_factor_enforcement
github_project_service_integration
group_allowed_email_domains
group_coverage_reports
group_forking_protection
group_merge_request_analytics
group_milestone_project_releases
group_project_templates
group_repository_analytics
group_saml
group_saml_group_sync
group_scoped_ci_variables
group_wikis
incident_sla
incident_metric_upload
ide_schema_config
issues_analytics
jira_issues_integration
ldap_group_sync_filter
merge_pipelines
merge_request_performance_metrics
admin_merge_request_approvers_rules
merge_trains
metrics_reports
multiple_alert_http_integrations
multiple_approval_rules
multiple_group_issue_boards
multiple_iteration_cadences
object_storage
operations_dashboard
package_forwarding
pages_size_limit
productivity_analytics
project_aliases
protected_environments
reject_unsigned_commits
required_ci_templates
scoped_labels
smartcard_auth
swimlanes
type_of_work_analytics
minimal_access_role
unprotection_restrictions
ci_project_subscriptions
incident_timeline_view
oncall_schedules
escalation_policies
export_user_permissions
zentao_issues_integration
]
EEP_FEATURES.freeze
EEU_FEATURES = EEP_FEATURES + %i[
api_fuzzing
auto_rollback
cilium_alerts
cluster_image_scanning
external_status_checks
container_scanning
coverage_fuzzing
credentials_inventory
dast
dependency_scanning
devops_adoption
dora4_analytics
enforce_personal_access_token_expiration
enforce_ssh_key_expiration
enterprise_templates
environment_alerts
evaluate_group_level_compliance_pipeline
external_audit_events
group_ci_cd_analytics
group_level_compliance_dashboard
group_level_devops_adoption
incident_management
incident_timeline_events
inline_codequality
insights
instance_level_devops_adoption
issuable_health_status
jira_vulnerabilities_integration
jira_issue_association_enforcement
kubernetes_cluster_vulnerabilities
license_scanning
personal_access_token_expiration_policy
project_quality_summary
prometheus_alerts
pseudonymizer
quality_management
related_epics
release_evidence_test_artifacts
report_approver_rules
requirements
sast
sast_iac
sast_custom_rulesets
sast_fp_reduction
secret_detection
security_configuration_in_ui
security_dashboard
security_on_demand_scans
security_orchestration_policies
ssh_key_expiration_policy
status_page
subepics
threat_monitoring
vulnerability_auto_fix
vulnerability_finding_signatures
]
EEU_FEATURES.freeze
FEATURES_BY_PLAN = {
STARTER_PLAN => EES_FEATURES,
PREMIUM_PLAN => EEP_FEATURES,
ULTIMATE_PLAN => EEU_FEATURES
}.freeze
PLANS_BY_FEATURE = FEATURES_BY_PLAN.each_with_object({}) do |(plan, features), hash|
features.each do |feature|
hash[feature] ||= []
hash[feature] << plan
end
end.freeze
FEATURES_WITH_USAGE_PING = EES_FEATURES_WITH_USAGE_PING + EEP_FEATURES_WITH_USAGE_PING
# Add on codes that may occur in legacy licenses that don't have a plan yet.
FEATURES_FOR_ADD_ONS = {
'GitLab_Auditor_User' => :auditor_user,
'GitLab_FileLocks' => :file_locks,
'GitLab_Geo' => :geo
}.freeze
# Global features that cannot be restricted to only a subset of projects or namespaces.
# Use `License.feature_available?(:feature)` to check if these features are available.
# For all other features, use `project.feature_available?` or `namespace.feature_available?` when possible.
GLOBAL_FEATURES = %i[
admin_audit_log
auditor_user
custom_file_templates
custom_project_templates
db_load_balancing
default_branch_protection_restriction_in_groups
elastic_search
enterprise_templates
extended_audit_events
external_authorization_service_api_management
geo
ldap_group_sync
ldap_group_sync_filter
multiple_ldap_servers
object_storage
pages_size_limit
project_aliases
repository_size_limit
required_ci_templates
seat_link
usage_quotas
].freeze
ACTIVE_USER_COUNT_THRESHOLD_LEVELS = [
{ range: (2..15), percentage: false, value: 1 },
{ range: (16..25), percentage: false, value: 2 },
......@@ -287,28 +50,6 @@ class License < ApplicationRecord
CACHE_KEY = :current_license
class << self
def features_for_plan(plan)
FEATURES_BY_PLAN.fetch(plan, [])
end
def plans_with_feature(feature)
if global_feature?(feature)
raise ArgumentError, "Use `License.feature_available?` for features that cannot be restricted to only a subset of projects or namespaces"
end
PLANS_BY_FEATURE.fetch(feature, [])
end
def features_with_usage_ping
return FEATURES_WITH_USAGE_PING if Gitlab::CurrentSettings.usage_ping_features_enabled?
[]
end
def plan_includes_feature?(plan, feature)
plans_with_feature(feature).include?(plan)
end
def current
cache.fetch(CACHE_KEY, as: License, expires_in: 1.minute) { load_license }
end
......@@ -348,10 +89,6 @@ class License < ApplicationRecord
Gitlab::SafeRequestStore.delete(:future_dated_license)
end
def global_feature?(feature)
GLOBAL_FEATURES.include?(feature)
end
def eligible_for_trial?
Gitlab::CurrentSettings.license_trial_ends_on.nil?
end
......@@ -456,12 +193,8 @@ class License < ApplicationRecord
restricted_attr(:reconciliation_completed)
end
def features_from_add_ons
add_ons.map { |name, count| FEATURES_FOR_ADD_ONS[name] if count.to_i > 0 }.compact
end
def features
@features ||= (self.class.features_for_plan(plan) + features_from_add_ons).to_set
@features ||= GitlabSubscriptions::Features.features(plan: plan, add_ons: add_ons)
end
def feature_available?(feature)
......
# frozen_string_literal: true
module Subscriptions
module GitlabSubscriptions
class CreateService
include Gitlab::Utils::StrongMemoize
......
......@@ -13,7 +13,7 @@ module EE
def validate_feature_flag_name!(name)
super
if License::PLANS_BY_FEATURE[name.to_sym]
if GitlabSubscriptions::Features::PLANS_BY_FEATURE[name.to_sym]
bad_request!(
"The '#{name}' is a licensed feature name, " \
"and thus it cannot be used as a feature flag name. " \
......
......@@ -12,7 +12,7 @@ module Gitlab
end
def allows_current_ip?
return true unless group&.feature_available?(:group_ip_restriction) || ::License.features_with_usage_ping.include?(:group_ip_restriction)
return true unless group&.feature_available?(:group_ip_restriction) || ::GitlabSubscriptions::Features.usage_ping_feature?(:group_ip_restriction)
current_ip_address = Gitlab::IpAddressState.current
......
......@@ -273,7 +273,7 @@ RSpec.describe SubscriptionsController do
before do
sign_in(user)
allow_any_instance_of(Subscriptions::CreateService).to receive(:execute).and_return(service_response)
allow_any_instance_of(GitlabSubscriptions::CreateService).to receive(:execute).and_return(service_response)
allow_any_instance_of(EE::Groups::CreateService).to receive(:execute).and_return(group)
end
......
......@@ -42,7 +42,7 @@ RSpec.describe EE::Gitlab::GonHelper do
end
describe '#push_licensed_feature' do
let_it_be(:feature) { License::EEU_FEATURES.first }
let_it_be(:feature) { GitlabSubscriptions::Features::ALL_FEATURES.first }
shared_examples 'sets the licensed features flag' do
it 'pushes the licensed feature flag to the frotnend' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSubscriptions::Features do
describe 'License -> Plan mapping' do
(::Plan.all_plans - ::Plan.default_plans).each do |plan_name|
describe "#{plan_name} plan" do
it 'is mapped to a license tier' do
expect(described_class::LICENSE_PLANS_TO_SAAS_PLANS.values.flatten).to include(plan_name)
end
end
end
end
describe '.plans_with_feature' do
subject { described_class.plans_with_feature(feature) }
context 'when params is a Starter feature' do
let(:feature) { described_class::STARTER_FEATURES.sample }
it { is_expected.to contain_exactly(License::STARTER_PLAN, License::PREMIUM_PLAN, License::ULTIMATE_PLAN) }
end
context 'when params is a Premium feature' do
let(:feature) { described_class::PREMIUM_FEATURES.sample }
it { is_expected.to contain_exactly(License::PREMIUM_PLAN, License::ULTIMATE_PLAN) }
end
context 'when params is a Ultimate feature' do
let(:feature) { described_class::ULTIMATE_FEATURES.sample }
it { is_expected.to contain_exactly(License::ULTIMATE_PLAN) }
end
context 'when param is a global feature' do
let(:feature) { described_class::GLOBAL_FEATURES.sample }
it { expect { subject }.to raise_error(ArgumentError) }
end
context 'when feature does not exist' do
let(:feature) { :does_not_exist }
it { is_expected.to be_empty }
end
end
describe '.saas_plans_with_feature' do
subject { described_class.saas_plans_with_feature(feature) }
context 'a Starter feature' do
let(:feature) { :audit_events }
it 'is present in all paid plans' do
expect(subject).to contain_exactly(*::Plan::PAID_HOSTED_PLANS)
end
end
context 'a Premium feature' do
let(:feature) { :epics }
it 'is present in all Premium+ plans' do
expected_plans = ::Plan::PAID_HOSTED_PLANS - [::Plan::BRONZE]
expect(subject).to contain_exactly(*expected_plans)
end
end
context 'an Ultimate feature' do
let(:feature) { :dast }
it 'is present in all top plans' do
expected_plans = ::Plan::PAID_HOSTED_PLANS - [::Plan::BRONZE, ::Plan::SILVER, ::Plan::PREMIUM, ::Plan::PREMIUM_TRIAL]
expect(subject).to contain_exactly(*expected_plans)
end
end
context 'a global feature' do
let(:feature) { :elastic_search }
it 'cannot be checked using this method' do
expect { subject }.to raise_error(ArgumentError)
end
end
context 'a non existing feature' do
let(:feature) { :unknown_feature }
it 'is not in any plan' do
expect(subject).to be_empty
end
end
end
describe '.global?' do
subject { described_class.global?(feature) }
context 'when it is a global feature' do
let(:feature) { :geo }
it { is_expected.to be(true) }
end
context 'when it is not a global feature' do
let(:feature) { :sast }
it { is_expected.to be(false) }
end
end
describe '.features' do
subject(:features) { described_class.features(plan: plan, add_ons: add_ons) }
let(:add_ons) { {} }
let_it_be(:starter_feature) { described_class::STARTER_FEATURES.sample }
let_it_be(:premium_feature) { described_class::PREMIUM_FEATURES.sample }
let_it_be(:ultimate_feature) { described_class::ULTIMATE_FEATURES.sample }
let_it_be(:add_on_feature) { :geo }
let_it_be(:geo_addon) { { 'GitLab_Geo' => 1 } }
context 'when plan is Starter' do
let(:plan) { License::STARTER_PLAN }
it 'includes only Starter features' do
expect(features).to include(starter_feature)
expect(features).not_to include(premium_feature)
expect(features).not_to include(ultimate_feature)
end
context 'when add-ons are present' do
let(:add_ons) { geo_addon }
it 'includes only Starter features' do
expect(features).to include(starter_feature)
expect(features).not_to include(premium_feature)
expect(features).not_to include(ultimate_feature)
end
it 'includes also add-on features' do
expect(features).to include(add_on_feature)
end
end
end
context 'when plan is Premium' do
let(:plan) { License::PREMIUM_PLAN }
it 'includes Starter and Premium features' do
expect(features).to include(starter_feature)
expect(features).to include(premium_feature)
expect(features).not_to include(ultimate_feature)
end
context 'when add-ons are present' do
let(:add_ons) { geo_addon }
it 'includes Starter and Premium features' do
expect(features).to include(starter_feature)
expect(features).to include(premium_feature)
expect(features).not_to include(ultimate_feature)
end
it 'includes also add-on features' do
expect(features).to include(add_on_feature)
end
end
end
context 'when plan is Ultimate' do
let(:plan) { License::ULTIMATE_PLAN }
it 'includes Starter, Premium and Ultimate features' do
expect(features).to include(starter_feature)
expect(features).to include(premium_feature)
expect(features).to include(ultimate_feature)
end
context 'when add-ons are present' do
let(:add_ons) { geo_addon }
it 'includes Starter, Premium and Ultimate features' do
expect(features).to include(starter_feature)
expect(features).to include(premium_feature)
expect(features).to include(ultimate_feature)
end
it 'includes also add-on features' do
expect(features).to include(add_on_feature)
end
end
end
end
describe '.usage_ping_feature?' do
subject { described_class.usage_ping_feature?(feature) }
let(:usage_ping_enabled) { true }
before do
stub_application_setting(usage_ping_features_enabled: usage_ping_enabled)
end
context 'when param is a Starter usage ping feature' do
let(:feature) { described_class::STARTER_FEATURES_WITH_USAGE_PING.sample }
it { is_expected.to be_truthy }
context 'when usage ping setting is disabled' do
let(:usage_ping_enabled) { false }
it { is_expected.to be_falsey }
end
end
context 'when param is a Premium usage ping feature' do
let(:feature) { described_class::PREMIUM_FEATURES_WITH_USAGE_PING.sample }
it { is_expected.to be_truthy }
context 'when usage ping setting is disabled' do
let(:usage_ping_enabled) { false }
it { is_expected.to be_falsey }
end
end
context 'when param is another usage ping feature' do
let(:feature) { :audit_events }
it { is_expected.to be_falsey }
end
end
end
......@@ -547,53 +547,6 @@ RSpec.describe License do
described_class.reset_current
end
describe '.features_for_plan' do
it 'returns features for starter plan' do
expect(described_class.features_for_plan('starter'))
.to include(:multiple_issue_assignees)
end
it 'returns features for premium plan' do
expect(described_class.features_for_plan('premium'))
.to include(:multiple_issue_assignees, :cluster_deployments, :file_locks, :group_wikis)
end
it 'returns empty array if no features for given plan' do
expect(described_class.features_for_plan('bronze')).to eq([])
end
end
describe '.plan_includes_feature?' do
let(:feature) { :cluster_deployments }
subject { described_class.plan_includes_feature?(plan, feature) }
context 'when addon included' do
let(:plan) { 'premium' }
it { is_expected.to eq(true) }
end
context 'when addon not included' do
let(:plan) { 'starter' }
it { is_expected.to eq(false) }
end
context 'when plan is not set' do
let(:plan) { nil }
it { is_expected.to eq(false) }
end
context 'when feature does not exists' do
let(:plan) { 'premium' }
let(:feature) { nil }
it { is_expected.to eq(false) }
end
end
describe '.current', :request_store, :use_clean_rails_memory_store_caching do
context 'when licenses table does not exist' do
it 'returns nil' do
......@@ -733,22 +686,6 @@ RSpec.describe License do
end
end
describe '.global_feature?' do
subject { described_class.global_feature?(feature) }
context 'when it is a global feature' do
let(:feature) { :geo }
it { is_expected.to be(true) }
end
context 'when it is not a global feature' do
let(:feature) { :sast }
it { is_expected.to be(false) }
end
end
describe '.with_valid_license' do
context 'when license trial' do
before do
......@@ -925,32 +862,6 @@ RSpec.describe License do
end
end
describe '#features_from_add_ons' do
context 'without add-ons' do
it 'returns an empty array' do
license = build_license_with_add_ons({}, plan: 'unknown')
expect(license.features_from_add_ons).to eq([])
end
end
context 'with add-ons' do
it 'returns all available add-ons' do
license = build_license_with_add_ons({ 'GitLab_FileLocks' => 2 })
expect(license.features_from_add_ons).to eq([:file_locks])
end
end
context 'with nil add-ons' do
it 'returns an empty array' do
license = build_license_with_add_ons({ 'GitLab_FileLocks' => nil })
expect(license.features_from_add_ons).to eq([])
end
end
end
describe '#feature_available?' do
it 'returns true if add-on exists and have a quantity greater than 0' do
license = build_license_with_add_ons({ 'GitLab_FileLocks' => 1 })
......@@ -983,7 +894,7 @@ RSpec.describe License do
described_class.delete_all
end
::License::EES_FEATURES.each do |feature|
::GitlabSubscriptions::Features::ALL_STARTER_FEATURES.each do |feature|
it "returns false for #{feature}" do
expect(license.feature_available?(feature)).to eq(false)
end
......
......@@ -1093,11 +1093,11 @@ RSpec.describe Project do
allow(namespace).to receive(:plan) { plan_license }
end
License::EEU_FEATURES.each do |feature_sym|
GitlabSubscriptions::Features::ALL_FEATURES.each do |feature_sym|
context feature_sym.to_s do
let(:feature) { feature_sym }
unless License::GLOBAL_FEATURES.include?(feature_sym)
unless GitlabSubscriptions::Features::GLOBAL_FEATURES.include?(feature_sym)
context "checking #{feature_sym} availability both on Global and Namespace license" do
let(:check_namespace_plan) { true }
......@@ -1126,7 +1126,7 @@ RSpec.describe Project do
end
end
unless License.plan_includes_feature?(License::STARTER_PLAN, feature_sym)
unless GitlabSubscriptions::Features.plans_with_feature(feature_sym).include?(License::STARTER_PLAN)
context 'not allowed by Plan License' do
let(:allowed_on_global_license) { true }
let(:plan_license) { build(:bronze_plan) }
......
......@@ -38,7 +38,7 @@ RSpec.describe API::Features, stub_feature_flags: false do
context 'when licensed feature name is given' do
let(:feature_name) do
License::PLANS_BY_FEATURE.each_key.first
GitlabSubscriptions::Features::PLANS_BY_FEATURE.each_key.first
end
it 'returns bad request' do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Subscriptions::CreateService do
RSpec.describe GitlabSubscriptions::CreateService do
subject(:execute) { described_class.new(user, group: group, customer_params: customer_params, subscription_params: subscription_params).execute }
let_it_be(:user) { create(:user, id: 111, first_name: 'First name', last_name: 'Last name', email: 'first.last@gitlab.com') }
......
......@@ -15,8 +15,7 @@ module EE
prepended do
def stub_licensed_features(features)
# EEU_FEATURES contains all the features we know about
missing_features = features.keys.map(&:to_sym) - License::EEU_FEATURES
missing_features = features.keys.map(&:to_sym) - GitlabSubscriptions::Features::ALL_FEATURES
if missing_features.any?
subject = missing_features.join(', ')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment